Merge branch 'develop' into feat/ia-nav-improvement

This commit is contained in:
Kent Wang
2026-05-20 00:06:58 +08:00
159 changed files with 1258 additions and 1020 deletions

26
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"workspaces": [
"packages/insomnia-testing",
"packages/insomnia",
"packages/insomnia-analytics",
"packages/insomnia-api",
"packages/insomnia-inso",
"packages/insomnia-smoke-test",
@@ -11325,9 +11326,9 @@
"license": "MIT"
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
"integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==",
"version": "0.8.13",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
"integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -18409,6 +18410,10 @@
"resolved": "packages/insomnia",
"link": true
},
"node_modules/insomnia-analytics": {
"resolved": "packages/insomnia-analytics",
"link": true
},
"node_modules/insomnia-api": {
"resolved": "packages/insomnia-api",
"link": true
@@ -29183,7 +29188,6 @@
"@rjsf/utils": "6.0.0-beta.15",
"@rjsf/validator-ajv8": "6.0.0-beta.15",
"@seald-io/nedb": "^4.1.1",
"@segment/analytics-node": "2.2.1",
"@sentry/electron": "^6.5.0",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",
@@ -29336,6 +29340,13 @@
"@kong/insomnia-plugin-external-vault": "0.1.4-dev.20251224090833"
}
},
"packages/insomnia-analytics": {
"version": "12.5.1-alpha.0",
"license": "Apache-2.0",
"dependencies": {
"@segment/analytics-node": "2.2.1"
}
},
"packages/insomnia-api": {
"version": "12.5.1-alpha.0",
"license": "Apache-2.0",
@@ -29348,7 +29359,6 @@
"license": "Apache-2.0",
"dependencies": {
"@seald-io/nedb": "^4.1.1",
"@segment/analytics-node": "^2.2.1",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",
"@stoplight/spectral-ruleset-bundler": "1.7.0",
@@ -29938,9 +29948,9 @@
}
},
"packages/insomnia/node_modules/@xmldom/xmldom": {
"version": "0.9.9",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.9.tgz",
"integrity": "sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==",
"version": "0.9.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
"integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==",
"license": "MIT",
"engines": {
"node": ">=14.6"

View File

@@ -17,6 +17,7 @@
"workspaces": [
"packages/insomnia-testing",
"packages/insomnia",
"packages/insomnia-analytics",
"packages/insomnia-api",
"packages/insomnia-inso",
"packages/insomnia-smoke-test",

View File

@@ -0,0 +1,36 @@
{
"private": true,
"name": "insomnia-analytics",
"license": "Apache-2.0",
"version": "12.5.1-alpha.0",
"author": "Kong <office@konghq.com>",
"description": "Shared analytics client for the Insomnia desktop app and CLI",
"repository": {
"type": "git",
"url": "git+https://github.com/Kong/insomnia.git",
"directory": "packages/insomnia-analytics"
},
"bugs": {
"url": "https://github.com/kong/insomnia/issues"
},
"homepage": "https://github.com/Kong/insomnia#readme",
"sideEffects": false,
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"
},
"./events": {
"import": "./src/events.ts",
"types": "./src/events.ts"
}
},
"scripts": {
"lint": "eslint . --ext .ts --cache",
"type-check": "tsc --noEmit --project tsconfig.json",
"test": "vitest run"
},
"dependencies": {
"@segment/analytics-node": "2.2.1"
}
}

View File

@@ -0,0 +1,73 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const mockTrack = vi.fn();
const mockPage = vi.fn();
const mockCloseAndFlush = vi.fn().mockResolvedValue(void 0);
vi.mock('@segment/analytics-node', () => ({
Analytics: vi.fn(() => ({
track: mockTrack,
page: mockPage,
closeAndFlush: mockCloseAndFlush,
})),
}));
describe('InsomniaAnalytics', () => {
beforeEach(() => {
vi.resetModules();
mockTrack.mockReset();
mockPage.mockReset();
mockCloseAndFlush.mockReset().mockResolvedValue(void 0);
});
it('forwards track calls with platform-tagged properties and an app+os context', async () => {
const { InsomniaAnalytics } = await import('./analytics');
const analytics = new InsomniaAnalytics({
writeKey: 'test',
app: { appName: 'inso', appVersion: '1.2.3', osVersion: '14.0', platform: 'cli' },
});
analytics.track({ event: 'inso_run_test', properties: { suite: 'echo' }, anonymousId: 'anon-1' });
expect(mockTrack).toHaveBeenCalledTimes(1);
const [payload] = mockTrack.mock.calls[0];
expect(payload).toMatchObject({
event: 'inso_run_test',
anonymousId: 'anon-1',
userId: '',
properties: { suite: 'echo', platform: 'cli' },
context: { app: { name: 'inso', version: '1.2.3' }, os: { version: '14.0' } },
});
});
it('resolves osVersion lazily when provided as a function', async () => {
const { InsomniaAnalytics } = await import('./analytics');
let v = '1.0';
const analytics = new InsomniaAnalytics({
writeKey: 'test',
app: { appName: 'app', appVersion: '0.1', osVersion: () => v, platform: 'app' },
});
v = '2.0';
analytics.track({ event: 'App Started', anonymousId: 'a', userId: 'u' });
const [payload] = mockTrack.mock.calls[0];
expect(payload.context.os.version).toBe('2.0');
});
it('routes analytics errors through the onError handler', async () => {
const onError = vi.fn();
const { InsomniaAnalytics } = await import('./analytics');
const analytics = new InsomniaAnalytics({
writeKey: 'test',
app: { appName: 'app', appVersion: '0.1', osVersion: '1.0', platform: 'app' },
onError,
});
analytics.track({ event: 'App Started', anonymousId: 'a' });
const [, callback] = mockTrack.mock.calls[0];
callback(new Error('segment failure'));
expect(onError).toHaveBeenCalledWith(expect.any(Error));
});
});

View File

@@ -0,0 +1,103 @@
import { Analytics, type AnalyticsSettings } from '@segment/analytics-node';
export interface AppContext {
appName: string;
appVersion: string;
osVersion: string | (() => string);
platform: 'app' | 'cli';
}
export interface TrackOptions {
event: string;
properties?: Record<string, unknown>;
anonymousId: string;
userId?: string;
}
export interface PageOptions {
name: string;
anonymousId: string;
userId?: string;
}
export interface InsomniaAnalyticsOptions {
writeKey: string;
app: AppContext;
settings?: Omit<AnalyticsSettings, 'writeKey'>;
onError?: (error: unknown) => void;
}
export function getNormalizedOsName(platform: NodeJS.Platform | string): string {
switch (platform) {
case 'darwin': {
return 'mac';
}
case 'win32': {
return 'windows';
}
default: {
return platform;
}
}
}
export class InsomniaAnalytics {
private readonly client: Analytics;
private readonly app: AppContext;
private readonly onError: (error: unknown) => void;
constructor({ writeKey, app, settings, onError }: InsomniaAnalyticsOptions) {
this.client = new Analytics({ writeKey, ...settings });
this.app = app;
this.onError = onError ?? (() => {});
}
track({ event, properties, anonymousId, userId }: TrackOptions): void {
this.client.track(
{
event,
anonymousId,
userId: userId ?? '',
properties: { ...properties, platform: this.app.platform },
context: this.buildContext(),
},
error => {
if (error) {
this.onError(error);
}
},
);
}
page({ name, anonymousId, userId }: PageOptions): void {
this.client.page(
{
name,
anonymousId,
userId: userId ?? '',
context: this.buildContext(),
},
error => {
if (error) {
this.onError(error);
}
},
);
}
async closeAndFlush(timeoutMs = 5000): Promise<void> {
try {
await this.client.closeAndFlush({ timeout: timeoutMs });
} catch (error) {
this.onError(error);
}
}
private buildContext() {
const osVersion = typeof this.app.osVersion === 'function' ? this.app.osVersion() : this.app.osVersion;
return {
app: { name: this.app.appName, version: this.app.appVersion },
os: { name: getNormalizedOsName(process.platform), version: osVersion },
};
}
}

View File

@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { AnalyticsEvent, InsoEvent } from './events';
describe('events', () => {
it('AnalyticsEvent has expected entries', () => {
expect(AnalyticsEvent.appStarted).toBe('App Started');
expect(AnalyticsEvent.unitTestRun).toBe('Ran Individual Unit Test');
expect(AnalyticsEvent.installPlugin).toBe('Plugin Installed');
});
it('InsoEvent prefixes with inso_', () => {
expect(InsoEvent.runTest).toBe('inso_run_test');
expect(InsoEvent.script).toBe('inso_script');
});
});

View File

@@ -0,0 +1,142 @@
export enum AnalyticsEvent {
appStarted = 'App Started',
analyticsDisabled = 'Analytics Disabled',
collectionCreate = 'Collection Created',
dataExport = 'Data Exported',
exportCompleted = 'Export Completed',
dataImport = 'Data Imported',
importStarted = 'Import Started',
importScanned = 'Import Scanned',
importCompleted = 'Import Completed',
importLoginRequired = 'Import Login Required',
importResumedAfterLogin = 'Import Resumed After Login',
importedRequestFirstSend = 'Imported Request First Send',
documentCreate = 'Document Created',
mockCreateModalOpened = 'Mock Server Create Modal Opened',
mockCreate = 'Mock Created',
mockEdit = 'Mock Server Edited',
mockDelete = 'Mock Server Deleted',
mockRouteCreate = 'Mock Route Created',
mockRouteEdit = 'Mock Route Edited',
mockRouteDelete = 'Mock Route Deleted',
generateCollection = 'Generated Collection',
generateCollectionFromMock = 'Generate Collection From Mock',
environmentCreate = 'Environment Created',
loginSuccess = 'Login Success',
inviteTrigger = 'Invite Triggered From App',
exportAllCollections = 'Exported All Collections',
kongConnected = 'Kong Connected',
kongSync = 'Kong Synced',
requestBodyTypeSelect = 'Request Body Type Selected',
requestCreated = 'Request Created',
requestExecuted = 'Request Executed',
requestEdit = 'Request Edited',
requestDeleted = 'Request Deleted',
requestRenamed = 'Request Renamed',
requestUrlCopied = 'Request URL Copied',
collectionRunExecute = 'Collection Run Executed',
projectLocalCreate = 'Local Project Created',
projectLocalDelete = 'Local Project Deleted',
selectScratchpad = 'Scratchpad Selected at Login',
syncConflictResolutionStart = 'Sync Conflict Resolution Started',
syncConflictResolutionCompleteMine = 'Sync Conflict Resolution Completed Mine',
syncConflictResolutionCompleteTheirs = 'Sync Conflict Resolution Completed Theirs',
testSuiteCreate = 'Test Suite Created',
testSuiteDelete = 'Test Suite Deleted',
unitTestCreate = 'Unit Test Created',
unitTestDelete = 'Unit Test Deleted',
unitTestRun = 'Ran Individual Unit Test',
unitTestRunAll = 'Ran All Unit Tests',
vcsSyncStart = 'VCS Sync Started',
vcsSyncComplete = 'VCS Sync Completed',
vcsAction = 'VCS Action Executed',
gitAuthenticationCompleted = 'Git Authentication Completed',
gitAuthenticationUpdated = 'Git Authentication Updated',
buttonClick = 'Button Clicked',
inviteMember = 'Invite Sent',
inviteResent = 'Invite Resent',
inviteRevoked = 'Invite Revoked',
projectCreated = 'Project Created',
projectUpdated = 'Project Updated',
exportStarted = 'Export Started',
exportRequestsChosen = 'Export Requests Chosen',
recommendCommitsGenerated = 'Recommend Commits Generated',
recommendCommitsSaved = 'Recommend Commits Saved',
recommendCommitsCancelled = 'Recommend Commits Cancelled',
recommendCommitsClicked = 'Recommend Commits Clicked',
mcpClientWorkspaceCreate = 'MCP Client Workspace Created',
mcpClientAdded = 'MCP Client Added',
mcpClientConnected = 'MCP Client Connected',
mcpClientDisconnected = 'MCP Client Disconnected',
mcpToolCalled = 'MCP Tool Called',
mcpResourceRead = 'MCP Resource Read',
mcpPromptCalled = 'MCP Prompt Called',
inviteNotPermitted = 'Invite Not Permitted',
responseToMockClicked = 'Response To Mock Clicked',
gitSyncButtonClicked = 'Git Sync Button Clicked',
preferencesViewed = 'Preferences Viewed',
copyAsCurl = 'Copied As cURL',
themeChanged = 'Theme Changed',
generateCodeClicked = 'Generate Code Clicked',
generateCodeLanguageChanged = 'Generate Code Language Changed',
filterCreatedHomePage = 'Filter Created From Home Page',
filterCreatedProjects = 'Filter Created Projects',
filterCreatedRequests = 'Filter Created Requests',
filterCreatedResponseBody = 'Filter Created Response Body',
aiFeatureEnabled = 'AI Feature Enabled',
aiFeatureDisabled = 'AI Feature Disabled',
installPlugin = 'Plugin Installed',
homepageFiltered = 'homepage-filtered',
quickSearchOpenedByKeyboard = 'quick-search-opened-by-keyboard',
quickSearchOpenedByMouse = 'quick-search-opened-by-mouse',
statusbarLeftbarToggled = 'statusbar-leftbar-toggled',
statusbarTopbarToggled = 'statusbar-topbar-toggled',
statusbarOrphanedProjectsClicked = 'statusbar-orphaned-projects-clicked',
designerGenerateMockClicked = 'designer-generate-mock-clicked',
designerPreviewToggled = 'designer-preview-toggled',
requestEnvironmentClicked = 'request-environment-clicked',
requestAddCookiesClicked = 'request-add-cookies-clicked',
requestAddCertificatesClicked = 'request-add-certificates-clicked',
requestListSortClicked = 'request-list-sort-clicked',
requestListExpandCollapseClicked = 'request-list-expand-collapse-clicked',
requestParamsDescriptionToggled = 'request-params-description-toggled',
requestParamsImportFromURLClicked = 'request-params-import-from-URL-clicked',
requestParamsBulkEditToggled = 'request-params-bulk-edit-toggled',
responsePreviewJSONPathEntered = 'response-preview-jsonpath-entered',
requestBodyBeautifyClicked = 'request-body-beautify-clicked',
requestHeadersDescriptionToggled = 'request-headers-description-toggled',
requestHeadersBulkEditToggled = 'request-headers-bulk-edit-toggled',
requestScriptsPreScriptSnippetAdded = 'request-scripts-prescript-snippet-added',
requestScriptsPostScriptSnippetAdded = 'request-scripts-postscript-snippet-added',
responseHeadersCopyAllClicked = 'response-headers-copy-all-clicked',
responseCookiesManageCookiesClicked = 'response-cookies-manage-cookies-clicked',
requestOpenInNewTabClicked = 'request-open-in-new-tab-clicked',
requestListMenuPinClicked = 'request-list-menu-pin-clicked',
requestListMenuDuplicateClicked = 'request-list-menu-duplicate-clicked',
requestListMenuRenameClicked = 'request-list-menu-rename-clicked',
requestListMenuSettingsClicked = 'request-list-menu-settings-clicked',
requestSendMenuGenerateCodeClicked = 'request-send-menu-generate-code-clicked',
requestSendMenuSendAfterDelayClicked = 'request-send-menu-send-after-delay-clicked',
requestSendMenuRepeatAfterIntervalClicked = 'request-send-menu-repeat-after-interval-clicked',
requestSendMenuDownloadAfterSendClicked = 'request-send-menu-download-after-send-clicked',
requestSendMenuSendAndDownloadClicked = 'request-send-menu-send-and-download-clicked',
mcpListExpandCollapseClicked = 'mcp-list-expand-collapse-clicked',
mcpListFiltered = 'mcp-list-filtered',
mcpRequestParamsBeautifyClicked = 'mcp-request-params-beautify-clicked',
mcpRequestHeadersDescriptionToggled = 'mcp-request-headers-description-toggled',
mcpRequestRootsNotifyClicked = 'mcp-request-roots-notify-clicked',
mcpResponseHeadersCopyAllClicked = 'mcp-response-headers-copy-all-clicked',
kongKonnectPatValidated = 'kong-konnect-pat-validated',
kongKonnectSyncCompleted = 'kong-konnect-sync-completed',
emptyStateSendRequestClicked = 'empty-state-send-request-clicked',
emptyStateCreateDocumentClicked = 'empty-state-create-document-clicked',
}
export enum InsoEvent {
runTest = 'inso_run_test',
runCollection = 'inso_run_collection',
lintSpec = 'inso_lint_spec',
exportSpec = 'inso_export_spec',
script = 'inso_script',
}

View File

@@ -0,0 +1,2 @@
export * from './events';
export * from './analytics';

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2020",
"allowJs": false,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"isolatedModules": true,
"module": "ESNext",
"sourceMap": true,
"baseUrl": ".",
"rootDirs": ["."],
"lib": ["ES2023", "DOM"],
"types": [],
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": false,
"verbatimModuleSyntax": true,
"forceConsistentCasingInFileNames": true
}
}

View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
},
});

View File

@@ -26,7 +26,7 @@
"typescript": "~5.6.2"
},
"engines": {
"node": ">=22.0"
"node": ">=24.0"
}
},
"node_modules/@ai-sdk/gateway": {
@@ -9144,9 +9144,10 @@
"license": "ISC"
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",

View File

@@ -51,7 +51,6 @@
"shellwords": "^1.0.1"
},
"dependencies": {
"@segment/analytics-node": "^2.2.1",
"@seald-io/nedb": "^4.1.1",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",

View File

@@ -1,7 +1,7 @@
import os from 'node:os';
import { Analytics } from '@segment/analytics-node';
import { getSegmentWriteKey } from 'insomnia/src/common/constants';
import { InsoEvent, InsomniaAnalytics } from 'insomnia-analytics';
import { v4 as uuidv4 } from 'uuid';
import type { Settings } from '~/insomnia-data';
@@ -10,15 +10,18 @@ import packageJson from '../package.json';
import neDbAdapter from './db/adapters/ne-db-adapter';
import { getAppDataDir, getDefaultProductName } from './util';
export enum InsoEvent {
runTest = 'inso_run_test',
runCollection = 'inso_run_collection',
lintSpec = 'inso_lint_spec',
exportSpec = 'inso_export_spec',
script = 'inso_script',
}
export { InsoEvent };
const analytics = new InsomniaAnalytics({
writeKey: getSegmentWriteKey(),
app: {
appName: 'inso',
appVersion: process.env.VERSION || packageJson.version,
osVersion: () => os.release(),
platform: 'cli',
},
});
const analyticsClient = new Analytics({ writeKey: getSegmentWriteKey() });
let deviceId: string | null = null;
let localSettings: Settings | null = null;
@@ -54,25 +57,16 @@ const getDeviceId = async (): Promise<string> => {
return deviceId;
};
const getOsName = (): string => {
switch (process.platform) {
case 'darwin': {
return 'mac';
}
case 'win32': {
return 'windows';
}
default: {
return process.platform;
}
}
};
export const trackInsoEvent = async (event: InsoEvent, properties?: Record<string, unknown>): Promise<void> => {
if (process.env.NODE_ENV === 'test') {
return;
}
// new for v13 - provide a way to disable analytics
if (process.env.INSO_TELEMETRY_DISABLED) {
return;
}
const settings = await getLocalSettings();
if (settings && !settings.enableAnalytics) {
return;
@@ -80,40 +74,10 @@ export const trackInsoEvent = async (event: InsoEvent, properties?: Record<strin
try {
const anonymousId = await getDeviceId();
const version = process.env.VERSION || packageJson.version;
analyticsClient.track(
{
event,
anonymousId,
properties: {
...properties,
platform: 'cli',
},
context: {
app: {
name: 'inso',
version,
},
os: {
name: getOsName(),
version: os.release(),
},
},
},
() => {
// Silently fail
},
);
} catch {
// Silently fail
}
analytics.track({ event, anonymousId, properties });
} catch {}
};
export const flushAnalytics = async (): Promise<void> => {
try {
await analyticsClient.closeAndFlush({ timeout: 5000 });
} catch {
// Silently fail
}
await analytics.closeAndFlush(5000);
};

View File

Binary file not shown.

View File

@@ -5,8 +5,9 @@ import { test } from '../../playwright/test';
test('can send requests', async ({ page, insomnia }) => {
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
const statusTag = page.locator('[data-testid="response-status-tag"]:visible');
const statusTag = page.getByTestId('response-pane').getByTestId('response-status-tag');
const responseBody = page.getByTestId('response-pane');
const responsePreviewBody = page.locator('[data-testid="response-pane"] >> [data-testid="CodeEditor"]:visible');
await insomnia.projectPage.importFixture('smoke-test-collection.yaml');
@@ -36,10 +37,10 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
await expect.soft(responseBody).toContainText('"id": "1"');
await expect.soft(responsePreviewBody).toContainText('"id": "1"');
await page.getByRole('button', { name: 'Preview' }).click();
await page.getByRole('menuitem', { name: 'Raw Data' }).click();
await expect.soft(responseBody).toContainText('{"id":"1"}');
await expect.soft(responsePreviewBody).toContainText('{"id":"1"}');
await insomnia.navigationSidebar.clickRequestOrFolder('connects to event stream and shows ping response');
await expect
@@ -47,7 +48,7 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Connect' }).click();
await expect.soft(statusTag).toContainText('200 OK');
await page.getByRole('tab', { name: 'Console' }).click();
await page.getByTestId('response-pane').getByRole('tab', { name: 'Console' }).click();
await expect.soft(responseBody).toContainText('Connected to 127.0.0.1');
await page.getByTestId('request-pane').getByRole('button', { name: 'Disconnect' }).click();
@@ -61,7 +62,7 @@ test('can send requests', async ({ page, insomnia }) => {
await expect.soft(statusTag).toContainText('200 OK');
await page.getByRole('button', { name: 'Preview' }).click();
await page.getByRole('menuitem', { name: 'Raw Data' }).click();
await expect.soft(responseBody).toContainText('a,b,c');
await expect.soft(responsePreviewBody).toContainText('a,b,c');
await insomnia.navigationSidebar.clickRequestOrFolder('sends dummy.xml request and shows raw response');
await expect
@@ -71,8 +72,8 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
await expect.soft(responseBody).toContainText('xml version="1.0"');
await expect.soft(responseBody).toContainText('<LoginResult>');
await expect.soft(responsePreviewBody).toContainText('xml version="1.0"');
await expect.soft(responsePreviewBody).toContainText('<LoginResult>');
await insomnia.navigationSidebar.clickRequestOrFolder('sends dummy.pdf request and shows rich response');
await expect
@@ -82,13 +83,34 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
await page.getByRole('tab', { name: 'Console' }).click();
const pdfIframe = page.getByTestId('ResponsePDFView');
await expect.soft(pdfIframe).toBeVisible();
await expect.soft(pdfIframe).toHaveAttribute('src', /^blob:/);
// find Electron/Chromium's built-in PDF viewer extension
await expect
.poll(() => page.frames().some(f => f.url().startsWith('chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai')), {
timeout: 5000,
message: 'Expected Chromium built-in PDF viewer extension frame to mount inside the PDF preview iframe',
})
.toBe(true);
await expect.soft(pdfIframe).toHaveScreenshot('dummy-pdf-preview.png', {
animations: 'disabled',
maxDiffPixelRatio: 0.15, // 15% discrepancy allowed for CI/environment differences
timeout: 5000,
});
await page.getByTestId('response-pane').getByRole('tab', { name: 'Console' }).click();
await page.locator('pre').filter({ hasText: '< Content-Type: application/pdf' }).click();
await page.getByTestId('response-pane').getByRole('tab', { name: 'Preview' }).click();
await insomnia.navigationSidebar.clickRequestOrFolder('sends request with basic authentication');
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
await expect.soft(responseBody).toContainText('basic auth received');
await page.getByTestId('response-pane').getByRole('tab', { name: 'Preview' }).click();
await expect.soft(responsePreviewBody).toContainText('basic auth received');
await insomnia.navigationSidebar.clickRequestOrFolder('sends request with cookie and get cookie in response');
await expect
@@ -96,7 +118,7 @@ test('can send requests', async ({ page, insomnia }) => {
.toBeVisible();
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
await expect.soft(statusTag).toContainText('200 OK');
await page.getByRole('tab', { name: 'Console' }).click();
await page.getByTestId('response-pane').getByRole('tab', { name: 'Console' }).click();
await expect.soft(responseBody).toContainText('Set-Cookie: insomnia-test-cookie=value123');
await insomnia.navigationSidebar.clickRequestOrFolder('delayed request');

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -64,7 +64,6 @@
"@rjsf/utils": "6.0.0-beta.15",
"@rjsf/validator-ajv8": "6.0.0-beta.15",
"@seald-io/nedb": "^4.1.1",
"@segment/analytics-node": "2.2.1",
"@sentry/electron": "^6.5.0",
"@stoplight/spectral-core": "^1.22.0",
"@stoplight/spectral-formats": "^1.8.2",

View File

@@ -66,8 +66,8 @@ vi.mock('../main/install-plugin', async () => {
});
vi.mock('../main/analytics', () => ({
trackSegmentEvent: vi.fn(),
SegmentEvent: {
trackAnalyticsEvent: vi.fn(),
AnalyticsEvent: {
installPlugin: 'Plugin Installed',
},
}));

View File

@@ -26,11 +26,7 @@ export async function absorbKey(sessionId: string, key: string) {
getUserProfile({ sessionId: sessionIdResolved }),
getEncryptionKeys({ sessionId: sessionIdResolved }),
]);
const {
public_key: publicKey,
enc_private_key: encPrivateKey,
enc_symmetric_key: encSymmetricKey,
} = keys;
const { public_key: publicKey, enc_private_key: encPrivateKey, enc_symmetric_key: encSymmetricKey } = keys;
const { email, id: accountId, first_name: firstName, last_name: lastName } = profile;
const symmetricKeyStr = crypt.decryptAES(key, JSON.parse(encSymmetricKey));
@@ -67,7 +63,7 @@ export async function getPrivateKey() {
}
export async function getCurrentSessionId() {
const { id } = await services.userSession.getOrCreate();
const { id } = await services.userSession.get();
return id;
}
@@ -122,16 +118,14 @@ export async function setSessionData(
lastName,
};
const userData = await services.userSession.getOrCreate();
await services.userSession.update(userData, sessionData);
await services.userSession.update(sessionData);
return sessionData;
}
/** Update the session data with vault salt and vault key */
export async function setVaultSessionData(vaultSalt: string, vaultKey: string) {
const userData = await services.userSession.getOrCreate();
await services.userSession.update(userData, { vaultSalt, vaultKey });
await services.userSession.update({ vaultSalt, vaultKey });
}
// ~~~~~~~~~~~~~~~~ //
@@ -139,25 +133,13 @@ export async function setVaultSessionData(vaultSalt: string, vaultKey: string) {
// ~~~~~~~~~~~~~~~~ //
export async function getUserSession(): Promise<SessionData> {
const userData = await services.userSession.getOrCreate();
const userData = await services.userSession.get();
return userData;
}
async function _unsetSessionData() {
await services.userSession.getOrCreate();
await services.userSession.update(await services.userSession.getOrCreate(), {
id: '',
accountId: '',
email: '',
firstName: '',
lastName: '',
symmetricKey: {} as JsonWebKey,
publicKey: {} as JsonWebKey,
encPrivateKey: {} as crypt.AESMessage,
vaultSalt: '',
vaultKey: '',
});
await services.userSession.remove();
}
/**
@@ -272,12 +254,12 @@ export async function migrateFromLocalStorage() {
try {
const sessionData = JSON.parse(session) as SessionData;
const currentUserSession = await services.userSession.getOrCreate();
const currentUserSession = await services.userSession.get();
if (currentUserSession.id) {
console.warn('Session already exists, skipping migration');
} else {
await services.userSession.update(currentUserSession, sessionData);
await services.userSession.update(sessionData);
}
} catch (e) {
console.error('Failed to parse session data', e);

View File

@@ -12,7 +12,7 @@ import { getRenderedRequestAndContext } from '../render';
describe('export', () => {
beforeEach(async () => {
await db.init({ inMemoryOnly: true }, true);
await services.project.all();
await services.project.list();
await services.settings.getOrCreate();
});

View File

@@ -12,7 +12,6 @@ const reqGroupBuilder = createBuilder(requestGroupModelSchema);
describe('render tests', () => {
beforeEach(async () => {
await services.project.all();
await services.settings.getOrCreate();
envBuilder.reset();
reqGroupBuilder.reset();

View File

@@ -592,7 +592,7 @@ export const importResourcesToNewWorkspace = async ({
}): Promise<Workspace> => {
invariant(resourceCacheItem, 'No resources to import');
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const resources = resourceCacheItem.resources;
@@ -766,8 +766,7 @@ export async function findExistingImportedSpec(
}
| undefined
> {
const allProjects = await services.project.all();
const filteredProjects = organizationId ? allProjects.filter(p => p.parentId === organizationId) : allProjects;
const filteredProjects = await services.project.list({ organizationId });
// match active project first, then look in rest
const projectIds = new Set<string>();

View File

@@ -34,7 +34,7 @@ export async function fetchAndCacheOrganizationStorageRule(
}
}
const { id: sessionId } = await services.userSession.getOrCreate();
const { id: sessionId } = await services.userSession.get();
return await getOrganizationStorageRule({
organizationId,

View File

@@ -129,6 +129,9 @@ export async function buildRenderContext({
const keys = _getOrderedEnvironmentKeys(subObject);
for (const key of keys) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
/*
* If we're overwriting a string, try to render it first using the same key from the base
* environment to support same-variable recursion. This allows for the following scenario:

View File

@@ -21,7 +21,7 @@ import { registerLLMConfigServiceAPI } from '~/main/llm-config-service';
import { userDataFolder } from '../config/config.json';
import { getAppVersion, getProductName, isDevelopment } from './common/constants';
import { isMac } from './common/platform';
import { SegmentEvent, trackSegmentEvent } from './main/analytics';
import { AnalyticsEvent, trackAnalyticsEvent } from './main/analytics';
import { registerInsomniaProtocols } from './main/api.protocol';
import { backupIfNewerVersionAvailable } from './main/backup';
import { registerSyncHandlers } from './main/cloud-sync/ipc';
@@ -289,7 +289,7 @@ async function _createModelInstances() {
await services.stats.get();
await services.settings.getOrCreate();
try {
const scratchpadProject = await services.project.getById(models.project.SCRATCHPAD_PROJECT_ID);
const scratchpadProject = await services.project.get(models.project.SCRATCHPAD_PROJECT_ID);
const scratchPad = await services.workspace.getById(models.workspace.SCRATCHPAD_WORKSPACE_ID);
if (!scratchpadProject) {
console.log('[main] Initializing Scratch Pad Project');
@@ -365,7 +365,7 @@ async function _trackStats() {
const settings = await services.settings.get();
trackSegmentEvent(SegmentEvent.appStarted, {
trackAnalyticsEvent(AnalyticsEvent.appStarted, {
localProjects,
remoteProjects,
createdRequests: stats.createdRequests,

View File

@@ -289,7 +289,7 @@ const main: Window['main'] = {
secretStorage,
electronStorage,
sync,
trackSegmentEvent: options => ipcRenderer.send('trackSegmentEvent', options),
trackAnalyticsEvent: options => ipcRenderer.send('trackAnalyticsEvent', options),
trackPageView: options => ipcRenderer.send('trackPageView', options),
setCurrentOrganizationId: organizationId => ipcRenderer.send('analytics.setOrganizationId', organizationId),
showNunjucksContextMenu: options => ipcRenderer.send('show-nunjucks-context-menu', options),

View File

@@ -30,7 +30,6 @@ import { services } from '~/insomnia-data';
// @vitest-environment jsdom
describe('Request Model - Comprehensive Tests', () => {
beforeEach(async () => {
await services.project.all();
await services.settings.getOrCreate();
// Create test project for all tests

View File

@@ -15,6 +15,7 @@ import * as mcpResponseService from './mcp-response';
import * as mockRouteService from './mock-route';
import * as mockServerService from './mock-server';
import * as oAuth2TokenService from './o-auth-2-token';
import * as organizationService from './organization';
import * as pluginDataService from './plugin-data';
import * as projectService from './project';
import * as protoDirectoryService from './proto-directory';
@@ -59,6 +60,7 @@ export const servicesNodeImpl = {
mcpRequest: mcpRequestService,
mcpResponse: mcpResponseService,
oAuth2Token: oAuth2TokenService,
organization: organizationService,
pluginData: pluginDataService,
protoDirectory: protoDirectoryService,
protoFile: protoFileService,

View File

@@ -0,0 +1,60 @@
import { getOrganizations, type Organization } from 'insomnia-api';
import { models } from '~/insomnia-data';
import * as userSessionService from './user-session';
function sortOrganizations(accountId: string, organizations: Organization[]): Organization[] {
const home = organizations.find(
organization =>
models.organization.isPersonalOrganization(organization) &&
models.organization.isOwnerOfOrganization({
organization,
accountId,
}),
);
const myOrgs = organizations
.filter(
organization =>
!models.organization.isPersonalOrganization(organization) &&
models.organization.isOwnerOfOrganization({
organization,
accountId,
}),
)
.sort((a, b) => a.name.localeCompare(b.name));
const notMyOrgs = organizations
.filter(
organization =>
!models.organization.isOwnerOfOrganization({
organization,
accountId,
}),
)
.sort((a, b) => a.name.localeCompare(b.name));
return [...(home ? [home] : []), ...myOrgs, ...notMyOrgs];
}
/**
* List organizations from the Insomnia cloud API.
*/
export async function list(): Promise<Organization[]> {
const { id: sessionId, accountId } = await userSessionService.get();
if (!sessionId || !accountId) {
return [];
}
const result = await getOrganizations({ sessionId });
const organizations = result?.organizations ?? [];
return sortOrganizations(accountId, organizations);
}
/**
* Get a single organization by ID.
*/
export async function get(id: string): Promise<Organization | undefined> {
const all = await list();
return all.find(org => org.id === id);
}

View File

@@ -1,4 +1,4 @@
import type { Project } from '~/insomnia-data';
import type { Project, Query } from '~/insomnia-data';
import { database as db, models } from '~/insomnia-data';
const { type } = models.project;
@@ -7,30 +7,42 @@ export function create(patch: Partial<Project> = {}) {
return db.docCreate<Project>(type, patch);
}
export function getById(_id: string) {
return db.findOne<Project>(type, { _id });
export function list(options?: { gitRepositoryIds?: string[]; organizationId?: string }) {
const query: Query<Project> = {};
if (options?.organizationId) {
query.parentId = options.organizationId;
}
if (options?.gitRepositoryIds) {
const queryIds = options.gitRepositoryIds.flatMap(id => models.project.getQueryableGitRepositoryIds(id));
query.gitRepositoryId = { $in: queryIds };
}
return db.find<Project>(type, query);
}
export function get(id: string) {
return db.findOne<Project>(type, { _id: id });
}
export function getByRemoteId(remoteId: string) {
return db.findOne<Project>(type, { remoteId });
}
export function getAllByGitRepositoryIds(gitRepositoryIds: string[]) {
const queryIds = gitRepositoryIds.flatMap(id => models.project.getQueryableGitRepositoryIds(id));
return db.find<Project>(type, {
gitRepositoryId: { $in: queryIds },
});
}
const getProjectByIdOrProject = async (idOrProject: string | Project) => {
const project = typeof idOrProject === 'string' ? await get(idOrProject) : idOrProject;
if (!project) {
throw new Error(
`Project not found: ${typeof idOrProject === 'string' ? idOrProject : `_id=${idOrProject._id}, name=${idOrProject.name}`}`,
);
}
return project;
};
export function remove(project: Project) {
return db.remove(project);
}
export function update(project: Project, patch: Partial<Project>) {
export async function update(idOrProject: string | Project, patch: Partial<Project>) {
const project = await getProjectByIdOrProject(idOrProject);
return db.docUpdate(project, patch);
}
export async function all() {
const projects = await db.find<Project>(type);
return projects;
export async function remove(idOrProject: string | Project) {
const project = await getProjectByIdOrProject(idOrProject);
return db.remove(project);
}

View File

@@ -3,41 +3,23 @@ import { database as db, models } from '~/insomnia-data';
const { type } = models.userSession;
export async function all() {
let userList = await db.find<UserSession>(type);
if (userList?.length === 0) {
userList = [await getOrCreate()];
}
return userList;
}
async function create() {
const user = await db.docCreate<UserSession>(type);
return user;
}
export async function update(user: UserSession, patch: Partial<UserSession>) {
const updatedUser = await db.docUpdate<UserSession>(user, patch);
return updatedUser;
}
export async function patch(patch: Partial<UserSession>) {
const user = await getOrCreate();
const updatedUser = await db.docUpdate<UserSession>(user, patch);
return updatedUser;
}
export async function getOrCreate() {
export async function get() {
const result = await db.findOne<UserSession>(type);
if (!result) {
return await create();
const user = await db.docCreate<UserSession>(type);
return user;
}
return result;
}
export async function get() {
return getOrCreate();
export async function update(patch: Partial<UserSession>) {
const user = await get();
const updatedUser = await db.docUpdate<UserSession>(user, patch);
return updatedUser;
}
export async function remove() {
const user = await get();
await db.remove(user);
}

View File

@@ -1,7 +1,7 @@
/**
* Tests run against the in-memory NeDB initialized by setup-vitest.ts.
* fetch is mocked per-test to return shaped Konnect API responses.
* window.main is stubbed globally so trackSegmentEvent calls don't throw.
* window.main is stubbed globally so trackAnalyticsEvent calls don't throw.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -101,13 +101,13 @@ const konnectRequests = (docs: any[]) => docs.filter((r: any) => r.konnectRouteK
const konnectWorkspaces = (docs: any[]) => docs.filter((w: any) => w.konnectServiceId != null);
const konnectProjects = (docs: any[]) => docs.filter((p: any) => p.konnectControlPlaneId != null);
const trackSegmentEvent = vi.fn();
const trackAnalyticsEvent = vi.fn();
beforeEach(async () => {
// Re-init with fresh in-memory NeDB buckets — clean slate for every test.
await initDatabase(mainDatabase, { inMemoryOnly: true }, true);
resetV4Counter();
vi.stubGlobal('window', { main: { trackSegmentEvent } });
vi.stubGlobal('window', { main: { trackAnalyticsEvent } });
});
afterEach(() => {

View File

@@ -20,10 +20,10 @@ vi.mock('~/insomnia-data', () => ({
getById: vi.fn(),
},
project: {
getById: vi.fn(),
get: vi.fn(),
},
userSession: {
getOrCreate: vi.fn(),
get: vi.fn(),
},
workspaceMeta: {
getOrCreateByParentId: vi.fn(),
@@ -68,8 +68,8 @@ describe('sync-initialization', () => {
vi.clearAllMocks();
vi.mocked(services.workspace.getById).mockResolvedValue(workspace);
vi.mocked(services.project.getById).mockResolvedValue(project);
vi.mocked(services.userSession.getOrCreate).mockResolvedValue({ id: 'sess_123' } as any);
vi.mocked(services.project.get).mockResolvedValue(project);
vi.mocked(services.userSession.get).mockResolvedValue({ id: 'sess_123' } as any);
vi.mocked(services.workspaceMeta.getOrCreateByParentId).mockResolvedValue({ gitRepositoryId: null } as any);
vi.mocked(services.environment.getOrCreateForParentId).mockResolvedValue({} as any);
vi.mocked(services.cookieJar.getOrCreateForParentId).mockResolvedValue({} as any);
@@ -83,7 +83,7 @@ describe('sync-initialization', () => {
});
it('returns early when initializing a workspace backend project without a session', async () => {
vi.mocked(services.userSession.getOrCreate).mockResolvedValue({ id: null } as any);
vi.mocked(services.userSession.get).mockResolvedValue({ id: null } as any);
await initializeWorkspaceBackendProject({ workspaceId: workspace._id });

View File

@@ -1,8 +1,8 @@
import crypto from 'node:crypto';
import { Analytics } from '@segment/analytics-node';
import * as Sentry from '@sentry/electron/main';
import { net } from 'electron';
import { AnalyticsEvent, InsomniaAnalytics } from 'insomnia-analytics';
import { v4 as uuidv4 } from 'uuid';
import { services } from '~/insomnia-data';
@@ -15,7 +15,8 @@ import {
getSegmentWriteKey,
PLAYWRIGHT_TEST,
} from '../common/constants';
import { platform } from '../common/platform';
export { AnalyticsEvent };
let _currentOrganizationId: string | undefined;
@@ -23,18 +24,29 @@ export function setCurrentOrganizationId(id: string | undefined): void {
_currentOrganizationId = id;
}
const analytics = new Analytics({
const analytics = new InsomniaAnalytics({
writeKey: getSegmentWriteKey(),
httpClient: {
makeRequest(_options) {
return net.fetch(_options.url, {
method: _options.method,
headers: _options.headers,
body: _options.body,
signal: AbortSignal.timeout(_options.httpRequestTimeout),
});
app: {
appName: getProductName(),
appVersion: getAppVersion(),
osVersion: () => process.getSystemVersion(),
platform: 'app',
},
settings: {
httpClient: {
makeRequest(_options) {
return net.fetch(_options.url, {
method: _options.method,
headers: _options.headers,
body: _options.body,
signal: AbortSignal.timeout(_options.httpRequestTimeout),
});
},
},
},
onError: error => {
console.warn('[analytics] Error sending analytics event', error);
},
});
const getDeviceId = async () => {
@@ -42,97 +54,48 @@ const getDeviceId = async () => {
return settings.deviceId || (await services.settings.update(settings, { deviceId: uuidv4() })).deviceId;
};
export enum SegmentEvent {
appStarted = 'App Started',
collectionCreate = 'Collection Created',
dataExport = 'Data Exported',
dataImport = 'Data Imported',
loginSuccess = 'Login Success',
documentCreate = 'Document Created',
kongConnected = 'Kong Connected',
kongSync = 'Kong Synced',
requestBodyTypeSelect = 'Request Body Type Selected',
requestCreated = 'Request Created',
requestExecuted = 'Request Executed',
collectionRunExecute = 'Collection Run Executed',
projectLocalCreate = 'Local Project Created',
projectLocalDelete = 'Local Project Deleted',
testSuiteCreate = 'Test Suite Created',
testSuiteDelete = 'Test Suite Deleted',
unitTestCreate = 'Unit Test Created',
unitTestDelete = 'Unit Test Deleted',
unitTestRun = 'Ran Individual Unit Test',
unitTestRunAll = 'Ran All Unit Tests',
vcsSyncStart = 'VCS Sync Started',
vcsSyncComplete = 'VCS Sync Completed',
vcsAction = 'VCS Action Executed',
gitAuthenticationCompleted = 'Git Authentication Completed',
gitAuthenticationUpdated = 'Git Authentication Updated',
buttonClick = 'Button Clicked',
aiFeatureEnabled = 'AI Feature Enabled',
aiFeatureDisabled = 'AI Feature Disabled',
mcpClientConnected = 'MCP Client Connected',
mcpClientDisconnected = 'MCP Client Disconnected',
mcpToolCalled = 'MCP Tool Called',
mcpResourceRead = 'MCP Resource Read',
mcpPromptCalled = 'MCP Prompt Called',
installPlugin = 'Plugin Installed',
}
function hashString(input: string) {
return crypto.createHash('sha256').update(input).digest('hex');
}
export async function trackSegmentEvent(event: SegmentEvent, properties?: Record<string, any>) {
export async function trackAnalyticsEvent(event: AnalyticsEvent, properties?: Record<string, any>) {
if (PLAYWRIGHT_TEST) {
return;
}
const settings = await services.settings.getOrCreate();
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
if (!userSession?.hashedAccountId) {
userSession.hashedAccountId = userSession?.accountId ? hashString(userSession.accountId) : '';
}
const allowAnalytics = settings.enableAnalytics || userSession?.hashedAccountId;
if (allowAnalytics) {
try {
const anonymousId = (await getDeviceId()) ?? '';
const context = {
app: { name: getProductName(), version: getAppVersion() },
os: { name: _getOsName(), version: process.getSystemVersion() },
};
if (!allowAnalytics) {
return;
}
analytics.track(
{
event,
properties: {
...(_currentOrganizationId && { organization_id: _currentOrganizationId }),
...properties,
platform: 'app',
},
context,
anonymousId,
userId: userSession?.hashedAccountId || '',
try {
const anonymousId = (await getDeviceId()) ?? '';
analytics.track({
event,
properties: {
...(_currentOrganizationId && { organization_id: _currentOrganizationId }),
...properties,
},
anonymousId,
userId: userSession?.hashedAccountId || '',
});
} catch (error: unknown) {
console.warn('[analytics] Unexpected error while sending analytics event', error);
} finally {
if (!userSession?.hashedAccountId && [AnalyticsEvent.unitTestRun, AnalyticsEvent.unitTestRunAll].includes(event)) {
Sentry.captureException(`Run tests by anonymous`, {
tags: {
source: 'main/analytics',
},
error => {
if (error) {
console.warn('[analytics] Error sending segment event', error);
}
extra: {
organizationId: properties?.organizationId || '',
projectId: properties?.projectId || '',
},
);
} catch (error: unknown) {
console.warn('[analytics] Unexpected error while sending segment event', error);
} finally {
if (!userSession?.hashedAccountId && [SegmentEvent.unitTestRun, SegmentEvent.unitTestRunAll].includes(event)) {
Sentry.captureException(`Run tests by anonymous`, {
tags: {
source: 'main/analytics',
},
extra: {
organizationId: properties?.organizationId || '',
projectId: properties?.projectId || '',
},
});
}
});
}
}
}
@@ -142,54 +105,30 @@ export async function trackPageView(name: string) {
return;
}
const settings = await services.settings.getOrCreate();
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
if (!userSession?.hashedAccountId) {
userSession.hashedAccountId = userSession?.accountId ? hashString(userSession.accountId) : '';
}
const allowAnalytics = settings.enableAnalytics || userSession?.hashedAccountId;
if (allowAnalytics) {
try {
const anonymousId = (await getDeviceId()) ?? '';
const context = {
app: { name: getProductName(), version: getAppVersion() },
os: { name: _getOsName(), version: process.getSystemVersion() },
};
if (!allowAnalytics) {
return;
}
analytics.page({ name, context, anonymousId, userId: userSession?.hashedAccountId }, error => {
if (error) {
console.warn('[analytics] Error sending segment event', error);
}
try {
const anonymousId = (await getDeviceId()) ?? '';
analytics.page({ name, anonymousId, userId: userSession?.hashedAccountId });
if (userSession?.id) {
net.fetch(getApiBaseURL() + '/v1/telemetry/', {
method: 'POST',
headers: new Headers({
'X-Session-Id': userSession?.id,
'X-Insomnia-Client': getClientString(),
}),
});
if (userSession?.id) {
net.fetch(getApiBaseURL() + '/v1/telemetry/', {
method: 'POST',
headers: new Headers({
'X-Session-Id': userSession?.id,
'X-Insomnia-Client': getClientString(),
}),
});
}
} catch (error: unknown) {
console.warn('[analytics] Unexpected error while sending segment event', error);
}
}
}
// ~~~~~~~~~~~~~~~~~ //
// Private Functions //
// ~~~~~~~~~~~~~~~~~ //
function _getOsName() {
switch (platform) {
case 'darwin': {
return 'mac';
}
case 'win32': {
return 'windows';
}
default: {
return platform;
}
} catch (error: unknown) {
console.warn('[analytics] Unexpected error while sending analytics event', error);
}
}

View File

@@ -11,7 +11,7 @@ export const initializeWorkspaceBackendProject = async ({ workspaceId }: { works
const workspace = await services.workspace.getById(workspaceId);
invariant(workspace, 'Workspace not found');
const { id } = await services.userSession.getOrCreate();
const { id } = await services.userSession.get();
if (!id) {
return;
}
@@ -32,10 +32,10 @@ export const syncNewWorkspaceIfNeeded = async ({ workspaceId }: { workspaceId: s
const workspace = await services.workspace.getById(workspaceId);
invariant(workspace, 'Workspace not found');
const project = await services.project.getById(workspace.parentId);
const project = await services.project.get(workspace.parentId);
invariant(project, 'Project not found');
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
if (!userSession.id || !models.project.isRemoteProject(project)) {
return;
}

View File

@@ -66,7 +66,7 @@ import { routableFSClient } from '../sync/git/routable-fs-client';
import { shallowClone } from '../sync/git/shallow-clone';
import type { AutoResolvedConflict, MergeConflict } from '../sync/types';
import { invariant } from '../utils/invariant';
import { SegmentEvent, trackSegmentEvent } from './analytics';
import { AnalyticsEvent, trackAnalyticsEvent } from './analytics';
import { ipcMainHandle } from './ipc/electron';
// Initialize Git Remote Providers on module load
@@ -153,7 +153,7 @@ export function getErrorMessage(error: unknown): string {
// Non-Error objects
return 'Unknown Error';
}
export function vcsSegmentEventProperties(type: 'git', action: VCSAction, error?: string) {
export function vcsEventProperties(type: 'git', action: VCSAction, error?: string) {
return { type, action, error };
}
@@ -216,7 +216,7 @@ async function getGitRepository({ projectId, workspaceId }: { projectId: string;
}
invariant(projectId, 'Project ID is required');
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
invariant(models.project.isConnectedGitProject(project), 'Project is not linked to a git repository');
const repoId = models.project.getEffectiveRepoId(project);
@@ -290,7 +290,7 @@ export async function getProjectGitFileIssues({
workspaceId,
gitRepositoryId,
}: GetProjectGitFileIssuesOptions): Promise<WorkspaceFileIssue[]> {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
if (!project || !models.project.isConnectedGitProject(project)) {
return [];
}
@@ -1097,8 +1097,8 @@ export const cloneGitRepoAction = async ({
}
if (!projectId) {
trackSegmentEvent(SegmentEvent.vcsSyncStart, {
...vcsSegmentEventProperties('git', 'clone'),
trackAnalyticsEvent(AnalyticsEvent.vcsSyncStart, {
...vcsEventProperties('git', 'clone'),
provider,
repoId: repoSettingsPatch._id,
});
@@ -1157,7 +1157,7 @@ export const cloneGitRepoAction = async ({
async function getProject() {
if (cloneIntoProjectId) {
const project = await services.project.getById(cloneIntoProjectId);
const project = await services.project.get(cloneIntoProjectId);
invariant(project, 'Project not found');
await services.project.update(project, {
@@ -1230,8 +1230,8 @@ export const cloneGitRepoAction = async ({
});
await database.flushChanges(bufferId);
trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
...vcsSegmentEventProperties('git', 'clone'),
trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
...vcsEventProperties('git', 'clone'),
providerName,
repoId: repoSettingsPatch._id,
});
@@ -1242,11 +1242,11 @@ export const cloneGitRepoAction = async ({
};
}
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
trackSegmentEvent(SegmentEvent.vcsSyncStart, {
...vcsSegmentEventProperties('git', 'clone'),
trackAnalyticsEvent(AnalyticsEvent.vcsSyncStart, {
...vcsEventProperties('git', 'clone'),
provider,
repoId: repoSettingsPatch._id,
});
@@ -1304,8 +1304,8 @@ export const cloneGitRepoAction = async ({
});
await services.apiSpec.getOrCreateForParentId(workspace._id);
trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
...vcsSegmentEventProperties('git', 'clone', 'no directory found'),
trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
...vcsEventProperties('git', 'clone', 'no directory found'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1323,8 +1323,8 @@ export const cloneGitRepoAction = async ({
const workspaces = await inMemoryFsClient.promises.readdir(workspaceBase);
if (workspaces.length === 0) {
trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
...vcsSegmentEventProperties('git', 'clone', 'no workspaces found'),
trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
...vcsEventProperties('git', 'clone', 'no workspaces found'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1335,8 +1335,8 @@ export const cloneGitRepoAction = async ({
}
if (workspaces.length > 1) {
trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
...vcsSegmentEventProperties('git', 'clone', 'multiple workspaces found'),
trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
...vcsEventProperties('git', 'clone', 'multiple workspaces found'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1359,7 +1359,7 @@ export const cloneGitRepoAction = async ({
const existingWorkspace = await services.workspace.getById(workspace._id);
if (existingWorkspace) {
const project = await services.project.getById(existingWorkspace.parentId);
const project = await services.project.get(existingWorkspace.parentId);
if (!project) {
return {
errors: [
@@ -1424,8 +1424,8 @@ export const cloneGitRepoAction = async ({
// Flush DB changes
await database.flushChanges(bufferId);
trackSegmentEvent(SegmentEvent.vcsSyncComplete, {
...vcsSegmentEventProperties('git', 'clone'),
trackAnalyticsEvent(AnalyticsEvent.vcsSyncComplete, {
...vcsEventProperties('git', 'clone'),
providerName: provider,
repoId: repoSettingsPatch._id,
});
@@ -1469,7 +1469,7 @@ export const updateGitRepoAction = async ({
const workspaceMeta = await services.workspaceMeta.getByParentId(workspaceId);
gitRepositoryId = workspaceMeta?.gitRepositoryId;
} else if (projectId) {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
gitRepositoryId = project.gitRepositoryId;
}
@@ -1497,7 +1497,7 @@ export const updateGitRepoAction = async ({
gitRepositoryId: gitRepository._id,
});
} else if (projectId) {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
await services.project.update(project, {
gitRepositoryId: models.project.toProtectedRepoId(gitRepository._id),
@@ -1557,7 +1557,7 @@ export const resetGitRepoAction = async ({ projectId, workspaceId }: { projectId
gitRepositoryId: null,
});
} else if (projectId) {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
await services.project.update(project, {
gitRepositoryId: models.project.EMPTY_GIT_PROJECT_ID,
@@ -1601,8 +1601,8 @@ export const commitToGitRepoAction = async ({
providerName = credentials.provider;
}
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'commit'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'commit'),
providerName,
repoId: gitRepository._id,
});
@@ -1730,8 +1730,8 @@ export const commitAndPushToGitRepoAction = async ({
providerName = credentials.provider;
}
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'commit'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'commit'),
providerName,
repoId: repo._id,
});
@@ -1770,8 +1770,8 @@ export const commitAndPushToGitRepoAction = async ({
try {
await GitVCS.push(repo.credentialsId);
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'push'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'push'),
providerName,
repoId: repo._id,
});
@@ -1804,8 +1804,8 @@ export const commitAndPushToGitRepoAction = async ({
}
const errorMessage = getErrorMessage(err);
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'push', errorMessage),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'push', errorMessage),
providerName,
repoId: repo._id,
});
@@ -1846,8 +1846,8 @@ export const createNewGitBranchAction = async ({
providerName = credentials.provider;
}
await GitVCS.checkout(branch);
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'create_branch'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'create_branch'),
providerName,
repoId: gitRepository._id,
});
@@ -2021,8 +2021,8 @@ export const mergeGitBranch = async ({
await repoFileWatcherRegistry.importAllFiles(gitRepoId);
clearConflictSuppression(gitRepository._id);
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'merge_branch'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'merge_branch'),
providerName,
repoId: gitRepository._id,
});
@@ -2050,8 +2050,8 @@ export const mergeGitBranch = async ({
errorMessage = `${err.message}, ${err.data.response}`;
}
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'merge_branch', errorMessage),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'merge_branch', errorMessage),
providerName,
repoId: gitRepository._id,
});
@@ -2086,8 +2086,8 @@ export const deleteGitBranchAction = async ({
providerName = credentials.provider;
}
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'delete_branch'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'delete_branch'),
providerName,
repoId: repo._id,
});
@@ -2163,8 +2163,8 @@ export const pushToGitRemoteAction = async ({
const bufferId = await database.bufferChanges();
await GitVCS.push(gitRepository.credentialsId);
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', force ? 'force_push' : 'push'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', force ? 'force_push' : 'push'),
providerName,
repoId: gitRepository._id,
});
@@ -2208,8 +2208,8 @@ export const pushToGitRemoteAction = async ({
}
const errorMessage = getErrorMessage(err);
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'push', errorMessage),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'push', errorMessage),
providerName,
repoId: gitRepository._id,
});
@@ -2277,8 +2277,8 @@ export async function pullFromGitRemote({ projectId, workspaceId }: { projectId:
await repoFileWatcherRegistry.importAllFiles(gitRepository._id);
clearConflictSuppression(repoId);
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'pull'),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'pull'),
providerName: credentials.provider,
repoId: gitRepository._id,
});
@@ -2330,8 +2330,8 @@ export async function pullFromGitRemote({ projectId, workspaceId }: { projectId:
providerName = credentials.provider;
}
trackSegmentEvent(SegmentEvent.vcsAction, {
...vcsSegmentEventProperties('git', 'pull', errorMessage),
trackAnalyticsEvent(AnalyticsEvent.vcsAction, {
...vcsEventProperties('git', 'pull', errorMessage),
providerName,
repoId: gitRepository._id,
});
@@ -2679,7 +2679,7 @@ const getRepositoryDirectoryTree = async ({
repositoryTree: FileTree;
folderList: Record<string, string[]>;
}> => {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
if (project && models.project.isEmptyGitProject(project)) {
return {
@@ -2788,9 +2788,9 @@ async function completeSignInToGitProvider({
}
if (isEditing) {
trackSegmentEvent(SegmentEvent.gitAuthenticationUpdated, { provider });
trackAnalyticsEvent(AnalyticsEvent.gitAuthenticationUpdated, { provider });
} else {
trackSegmentEvent(SegmentEvent.gitAuthenticationCompleted, { provider });
trackAnalyticsEvent(AnalyticsEvent.gitAuthenticationCompleted, { provider });
}
return {};
@@ -2893,7 +2893,7 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
const logs: string[] = [];
const failedProjects: { id: string; name: string }[] = [];
const allProjects = await services.project.all();
const allProjects = await services.project.list();
const gitProjects = allProjects.filter((p): p is GitProject => models.project.isConnectedGitProject(p));
if (gitProjects.length === 0) return { logs, failedProjects, totalProjects: 0 };
@@ -2947,7 +2947,7 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
failedProjects.map(async ({ id, name }) => {
logs.push(`${ts()} [INFO] ["${name}"] Converting to local project`);
try {
const project = await services.project.getById(id);
const project = await services.project.get(id);
if (!project || !models.project.isConnectedGitProject(project)) {
logs.push(`${ts()} [WARN] ["${name}"] Project not found or already local — skipping`);
return;

View File

@@ -7,7 +7,7 @@ import { promisify } from 'node:util';
import { app, net } from 'electron';
import { services } from '~/insomnia-data';
import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import { isDevelopment } from '../common/constants';
import { validatePluginName } from '../utils/plugin';
@@ -160,7 +160,7 @@ export default async function installPlugin(pluginName: string, allowScopedPacka
}),
);
trackSegmentEvent(SegmentEvent.installPlugin, {
trackAnalyticsEvent(AnalyticsEvent.installPlugin, {
pluginName: moduleName,
pluginVersion: info.version,
});

View File

@@ -192,7 +192,7 @@ export type MainOnChannels =
| 'socketIO.event.on'
| 'startExecution'
| 'trackPageView'
| 'trackSegmentEvent'
| 'trackAnalyticsEvent'
| 'updateLatestStepName'
| 'webSocket.close'
| 'webSocket.closeAll'

View File

@@ -37,8 +37,8 @@ import type {
import type { HiddenBrowserWindowBridgeAPI } from '../../entry.hidden-window';
import type { PluginTemplateTag, RenderedRequest } from '../../templating/types';
import type { SegmentEvent } from '../analytics';
import { setCurrentOrganizationId, trackPageView, trackSegmentEvent } from '../analytics';
import type { AnalyticsEvent } from '../analytics';
import { setCurrentOrganizationId, trackAnalyticsEvent, trackPageView } from '../analytics';
import {
authorizeUserInDefaultBrowser,
cancelAuthorizationInDefaultBrowser,
@@ -186,7 +186,7 @@ export interface RendererToMainBridgeAPI {
secretStorage: secretStorageBridgeAPI;
electronStorage: electronStorageBridgeAPI;
sync: SyncBridgeAPI;
trackSegmentEvent: (options: { event: string; properties?: Record<string, unknown> }) => void;
trackAnalyticsEvent: (options: { event: string; properties?: Record<string, unknown> }) => void;
trackPageView: (options: { name: string }) => void;
setCurrentOrganizationId: (organizationId: string | undefined) => void;
showNunjucksContextMenu: (options: {
@@ -399,10 +399,13 @@ export function registerMainHandlers() {
ipcMainHandle('readDir', readDir);
ipcMainHandle('readOrCreateDataDir', async (_, options: { folder: string }) => {
const dataPath = app.getPath('userData');
const folderPath = path.join(dataPath, options.folder);
const folderPath = path.join(app.getPath('userData'), options.folder);
mkdirSync(folderPath, { recursive: true });
return readDir(_, { path: folderPath });
try {
return await readDir(_, { path: folderPath });
} catch {
return [];
}
});
ipcMainHandle('curlRequest', (_, options: Parameters<typeof curlRequest>[0]) => {
@@ -413,9 +416,12 @@ export function registerMainHandlers() {
cancelCurlRequest(requestId);
});
ipcMainOn('trackSegmentEvent', (_, options: { event: SegmentEvent; properties?: Record<string, unknown> }): void => {
trackSegmentEvent(options.event, options.properties);
});
ipcMainOn(
'trackAnalyticsEvent',
(_, options: { event: AnalyticsEvent; properties?: Record<string, unknown> }): void => {
trackAnalyticsEvent(options.event, options.properties);
},
);
ipcMainOn('trackPageView', (_, options: { name: string }): void => {
trackPageView(options.name);
});

View File

@@ -4,7 +4,7 @@ import { app } from 'electron';
import { LLM_BACKENDS } from '~/common/constants';
import { services } from '~/insomnia-data';
import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import { ipcMainHandle } from '~/main/ipc/electron';
const LLM_PLUGIN_NAME = 'insomnia-llm';
@@ -123,7 +123,7 @@ export const getAIFeatureEnabled = async (feature: AIFeatureNames): Promise<bool
export const setAIFeatureEnabled = async (feature: AIFeatureNames, enabled: boolean): Promise<void> => {
await services.pluginData.upsertByKey(LLM_PLUGIN_NAME, `feature.${feature}`, String(enabled));
trackSegmentEvent(enabled ? SegmentEvent.aiFeatureEnabled : SegmentEvent.aiFeatureDisabled, {
trackAnalyticsEvent(enabled ? AnalyticsEvent.aiFeatureEnabled : AnalyticsEvent.aiFeatureDisabled, {
feature: feature,
set_for: 'user',
});

View File

@@ -11,7 +11,7 @@ import {
} from '@modelcontextprotocol/sdk/types.js';
import { METHOD_SUBSCRIBE_RESOURCE, METHOD_UNSUBSCRIBE_RESOURCE } from '~/common/mcp-utils';
import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import { getActiveMcpClient, getReadyActiveMcpConnectionContext, writeEventLogAndNotify } from '~/main/mcp/common';
import type { CommonMcpOptions, McpMessageEventWithoutBase } from '~/main/mcp/types';
@@ -29,7 +29,7 @@ export const callTool = async (options: CommonMcpOptions & CallToolRequest['para
const mcpClient = getActiveMcpClient(requestId);
if (mcpClient) {
const response = await mcpClient.callTool(params, CompatibilityCallToolResultSchema);
trackSegmentEvent(SegmentEvent.mcpToolCalled);
trackAnalyticsEvent(AnalyticsEvent.mcpToolCalled);
return response.content;
}
return null;
@@ -49,7 +49,7 @@ export const getPrompt = async (options: CommonMcpOptions & GetPromptRequest['pa
const mcpClient = getActiveMcpClient(options.requestId);
if (mcpClient) {
const prompt = await mcpClient.getPrompt(params);
trackSegmentEvent(SegmentEvent.mcpPromptCalled);
trackAnalyticsEvent(AnalyticsEvent.mcpPromptCalled);
return prompt;
}
return null;
@@ -80,7 +80,7 @@ export const readResource = async (options: CommonMcpOptions & ReadResourceReque
const mcpClient = getActiveMcpClient(requestId);
if (mcpClient) {
const resource = await mcpClient.readResource(params);
trackSegmentEvent(SegmentEvent.mcpResourceRead);
trackAnalyticsEvent(AnalyticsEvent.mcpResourceRead);
return resource;
}
return null;

View File

@@ -21,7 +21,7 @@ import electron from 'electron';
import { getAppVersion, getProductName, REALTIME_EVENTS_CHANNELS } from '~/common/constants';
import { getMcpMethodFromMessage, METHOD_NOTIFICATION_CANCELLED } from '~/common/mcp-utils';
import { models, services } from '~/insomnia-data';
import { SegmentEvent, trackSegmentEvent } from '~/main/analytics';
import { AnalyticsEvent, trackAnalyticsEvent } from '~/main/analytics';
import {
callTool,
getPrompt,
@@ -257,7 +257,7 @@ const createTransportAndConnect = async (context: ConnectionContext, mcpClient:
}
const authDisabled = 'disabled' in mcpRequest.authentication && mcpRequest.authentication.disabled;
const isFirstConnection = !mcpRequest.connected;
trackSegmentEvent(SegmentEvent.mcpClientConnected, {
trackAnalyticsEvent(AnalyticsEvent.mcpClientConnected, {
transportType: connectionOptions.transportType,
firstTime: isFirstConnection,
...(connectionOptions.transportType === models.mcpRequest.TRANSPORT_TYPES.HTTP
@@ -455,7 +455,7 @@ const closeMcpConnection = async (options: CommonMcpOptions) => {
// Execute clear resource subscription in main process rather than UI to make sure closeAllMcpConnections method will clear subscriptions
await services.mcpRequest.clearResourceSubscriptions(requestId);
}
trackSegmentEvent(SegmentEvent.mcpClientDisconnected);
trackAnalyticsEvent(AnalyticsEvent.mcpClientDisconnected);
};
const closeAllMcpConnections = () => {

View File

@@ -35,10 +35,6 @@ describe('getAuthQueryParams', () => {
});
});
describe('sendCurlAndWriteTimeline()', () => {
beforeEach(async () => {
await services.project.all();
});
it('sends a generic request', async () => {
const workspace = await services.workspace.create();
const settings = await services.settings.getOrCreate();

View File

@@ -35,7 +35,7 @@ import {
GIT_PROVIDER_COMPLETE_SIGN_IN_FETCHER_KEY,
useGitProviderCompleteSignInFetcher,
} from '~/routes/git-credentials.complete-sign-in';
import { PENDING_IMPORT_ATTRIBUTION_KEY, SegmentEvent, trackImportEvent } from '~/ui/analytics';
import { AnalyticsEvent, PENDING_IMPORT_ATTRIBUTION_KEY, trackImportEvent } from '~/ui/analytics';
import { getLoginUrl } from '~/ui/auth-session-provider.client';
import { CopyButton } from '~/ui/components/base/copy-button';
import { Link } from '~/ui/components/base/link';
@@ -161,7 +161,7 @@ export const useRootLoaderData = () => {
export async function clientLoader(_args: Route.ClientLoaderArgs) {
const settings = await services.settings.get();
const workspaceCount = await services.workspace.count();
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
const cloudCredentials = await services.cloudCredential.all();
return {
@@ -203,6 +203,11 @@ export const Layout = ({ children }: { children: React.ReactNode }) => {
*
insomnia://*
;
frame-src
blob:
*
insomnia://*
;
script-src
'self'
'unsafe-eval'
@@ -381,15 +386,14 @@ const Root = () => {
JSON.stringify({ importSource, importSourceUrl }),
);
}
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
if (!userSession.id) {
window.sessionStorage.setItem('pendingDeepLinkAfterAuthorize', url);
window.localStorage.setItem('logoutMessage', 'Please log in to import this resource.');
trackImportEvent(SegmentEvent.importLoginRequired);
trackImportEvent(AnalyticsEvent.importLoginRequired);
return navigate(href('/auth/login'));
}
trackImportEvent(SegmentEvent.importStarted, { source: 'import-url' });
trackImportEvent(AnalyticsEvent.importStarted, { source: 'import-url' });
if (params.uri) {
return setImportObject({
@@ -511,7 +515,7 @@ const Root = () => {
if (urlWithoutParams === 'insomnia://app/open/organization') {
// if user is logged out, navigate to authorize instead
// gracefully handle open org in app from browser
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
if (!userSession.id || userSession.id === '') {
const url = new URL(getLoginUrl());
window.main.openInBrowser(url.toString());
@@ -635,7 +639,7 @@ const Root = () => {
if (pendingDeepLink && organizationId && organizationId !== models.organization.SCRATCHPAD_ORGANIZATION_ID) {
window.sessionStorage.removeItem('pendingDeepLinkAfterAuthorize');
window.sessionStorage.setItem('suppressWelcomeModals', 'true');
trackImportEvent(SegmentEvent.importResumedAfterLogin);
trackImportEvent(AnalyticsEvent.importResumedAfterLogin);
window.main.openDeepLink(pendingDeepLink);
}
}, [organizationId]);

View File

@@ -1,6 +1,6 @@
import { href } from 'react-router';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { showToast } from '~/ui/components/toast-notification';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -38,8 +38,8 @@ export async function clientAction(args: Route.ClientActionArgs) {
.join('\n'),
});
window.main.trackSegmentEvent({
event: SegmentEvent.recommendCommitsGenerated,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.recommendCommitsGenerated,
properties: {
file_count: commits?.map(commit => commit.files?.length || 0)?.reduce((a, b) => a + b, 0),
group_count: commits?.length || 0,

View File

@@ -4,7 +4,7 @@ import { Button, Heading } from 'react-aria-components';
import { href, redirect, useFetchers, useNavigate } from 'react-router';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { getLoginUrl, submitAuthCode } from '~/ui/auth-session-provider.client';
import { Icon } from '~/ui/components/icon';
import { validateVaultKey } from '~/ui/vault-key.client';
@@ -32,18 +32,18 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
};
}
console.log('Login successful');
window.main.trackSegmentEvent({
event: SegmentEvent.loginSuccess,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.loginSuccess,
});
window.localStorage.setItem('hasUserLoggedInBefore', 'true');
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
const { accountId, id: sessionId } = userSession;
try {
// check vault salt exists in server
const { salt: vaultSalt } = await getVault({ sessionId });
if (vaultSalt) {
// save vault salt to session
await services.userSession.update(userSession, { vaultSalt });
await services.userSession.update({ vaultSalt });
// get vault key saved in local
const localVaultKey = await getVaultKeyFromStorage(accountId);
if (localVaultKey) {
@@ -52,7 +52,7 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
if (validateResult) {
// Encrypt vault key and save encrypted vault key & raw vault salt to session
const encryptedVaultKey = await window.main.secretStorage.encryptString(localVaultKey);
await services.userSession.update(userSession, { vaultKey: encryptedVaultKey, vaultSalt });
await services.userSession.update({ vaultKey: encryptedVaultKey, vaultSalt });
}
}
}

View File

@@ -10,7 +10,7 @@ import type { Route } from './+types/auth.clear-vault-key';
export async function clientAction({ request }: Route.ClientActionArgs) {
const { organizations = [], sessionId: resetVaultClientSessionId } = await request.json();
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
const { id: sessionId } = userSession;
const { salt: newVaultSalt } =
(await getVault({ sessionId }).catch(error => {
@@ -21,7 +21,7 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
// remove all secret environment variables
await services.environment.removeAllSecrets(organizations);
// Update vault salt and delete vault key from session
await services.userSession.update(userSession, { vaultSalt: newVaultSalt, vaultKey: '' });
await services.userSession.update({ vaultSalt: newVaultSalt, vaultKey: '' });
// show notification
electron.ipcRenderer.emit('show-toast', null, {
content: {

View File

@@ -3,7 +3,7 @@ import { Button } from 'react-aria-components';
import { href, redirect, useNavigate } from 'react-router';
import { models } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { getLoginUrl } from '~/ui/auth-session-provider.client';
import { Icon } from '~/ui/components/icon';
import { Tooltip } from '~/ui/components/tooltip';
@@ -152,8 +152,8 @@ const Component = () => {
<Button
onPress={() => {
window.main.trackSegmentEvent({
event: SegmentEvent.selectScratchpad,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.selectScratchpad,
});
navigate(
href('/organization/:organizationId/project/:projectId/workspace/:workspaceId/debug', {

View File

@@ -6,11 +6,11 @@ import { createFetcherSubmitHook } from '~/utils/router';
export async function clientAction(_args: ActionFunctionArgs) {
try {
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
const { id: sessionId } = userSession;
const { salt: vaultSalt } = await getVault({ sessionId });
if (vaultSalt) {
await services.userSession.update(userSession, { vaultSalt });
await services.userSession.update({ vaultSalt });
return vaultSalt;
}
} catch (error) {

View File

@@ -6,7 +6,7 @@ import { createFetcherSubmitHook } from '~/utils/router';
export async function clientAction({ request }: ActionFunctionArgs) {
const { vaultKey, saveVaultKey: saveVaultKeyLocally = false } = await request.json();
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
const { vaultSalt, accountId } = userSession;
if (!vaultSalt) {

View File

@@ -38,7 +38,7 @@ export async function clientLoader(args: Route.ClientLoaderArgs) {
);
};
const { accountId } = await services.userSession.getOrCreate();
const { accountId } = await services.userSession.get();
const allOrganizations = JSON.parse(localStorage.getItem(`${accountId}:organizations`) || '[]') as Organization[];

View File

@@ -12,7 +12,7 @@ export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const gitRepositoryIds = relatedGitRepositories.map(repo => repo._id);
const relatedProjects = await services.project.getAllByGitRepositoryIds(gitRepositoryIds);
const relatedProjects = await services.project.list({ gitRepositoryIds });
return {
projects: relatedProjects,

View File

@@ -90,7 +90,7 @@ const MigrationView = () => {
<p className="text-sm">We hit an unexpected error while updating your file system. Please try again.</p>
<p className="text-sm text-[#828282]">
Having trouble and need to contact us, or back up to an old version? See our{' '}
<ExternalLink className="underline" href="https://developer.konghq.com/insomnia/git-sync/">
<ExternalLink className="underline" href="https://developer.konghq.com/insomnia/upgrade/insomnia-12.6/">
docs.
</ExternalLink>
</p>
@@ -103,7 +103,7 @@ const MigrationView = () => {
</p>
<p className="text-sm">
Note: This change is backwards compatible, but we strongly recommend{' '}
<ExternalLink className="underline" href="https://developer.konghq.com/insomnia/git-sync/">
<ExternalLink className="underline" href="https://developer.konghq.com/insomnia/upgrade/insomnia-12.6/">
following these best practices
</ExternalLink>{' '}
when returning to an earlier version of Insomnia.

View File

@@ -7,7 +7,7 @@ import { models, services } from '~/insomnia-data';
import { createFetcherLoadHook } from '~/utils/router';
export async function clientLoader() {
const { accountId } = await services.userSession.getOrCreate();
const { accountId } = await services.userSession.get();
const organizations = JSON.parse(localStorage.getItem(`${accountId}:organizations`) || '[]') as Organization[];
const allProjects = (
await Promise.all(

View File

@@ -35,7 +35,7 @@ export const importScannedResources = async ({
invariant(organizationId && typeof organizationId === 'string', 'OrganizationId is required.');
invariant(projectId && typeof projectId === 'string', 'ProjectId is required.');
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found.');
return await (typeof workspaceId === 'string' && workspaceId

View File

@@ -9,7 +9,7 @@ import {
scanResources,
} from '~/common/import';
import type { ImportEntry } from '~/main/importers/entities';
import { SegmentEvent, trackImportEvent } from '~/ui/analytics';
import { AnalyticsEvent, trackImportEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -27,7 +27,7 @@ export const scanImportResources = async (data: {
invariant(typeof source === 'string', 'Source is required.');
invariant(IMPORT_SOURCE_TYPES.includes(source), 'Unsupported import type');
trackImportEvent(SegmentEvent.importScanned, { source });
trackImportEvent(AnalyticsEvent.importScanned, { source });
const contentList: ImportEntry[] = [];

View File

@@ -10,7 +10,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
const { organizationId, invitationId } = params;
try {
const user = await services.userSession.getOrCreate();
const user = await services.userSession.get();
const sessionId = user.id;
const response = await reinvite({

View File

@@ -16,7 +16,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
invariant(typeof roleId === 'string', 'Role ID is required');
try {
const user = await services.userSession.getOrCreate();
const user = await services.userSession.get();
const sessionId = user.id;
const response = await updateInvitationRole({

View File

@@ -16,7 +16,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
invariant(typeof roleId === 'string', 'Role ID is required');
try {
const user = await services.userSession.getOrCreate();
const user = await services.userSession.get();
const sessionId = user.id;
const response = await updateUserRoles({
organizationId,

View File

@@ -26,7 +26,7 @@ export const fallbackBilling = Object.freeze<Billing>({
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { organizationId } = params;
const { id: sessionId, accountId } = await services.userSession.getOrCreate();
const { id: sessionId, accountId } = await services.userSession.get();
if (models.organization.isScratchpadOrganizationId(organizationId)) {
return {

View File

@@ -37,7 +37,7 @@ import { useInsomniaSyncPullRemoteFileActionFetcher } from '~/routes/organizatio
import { useProjectLoaderData } from '~/routes/organization.$organizationId.project.$projectId';
import { useWorkspaceNewActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.new';
import { useStorageRulesLoaderFetcher } from '~/routes/organization.$organizationId.storage-rules';
import { SegmentEvent, trackOnceDaily } from '~/ui/analytics';
import { AnalyticsEvent, trackOnceDaily } from '~/ui/analytics';
import { AvatarGroup } from '~/ui/components/avatar';
import { WorkspaceCardDropdown } from '~/ui/components/dropdowns/workspace-card-dropdown';
import { ErrorBoundary } from '~/ui/components/error-boundary';
@@ -389,7 +389,7 @@ const Component = () => {
onChange={filter => {
setWorkspaceListFilter(filter);
if (filter.trim() !== '') {
trackOnceDaily(SegmentEvent.homepageFiltered);
trackOnceDaily(AnalyticsEvent.homepageFiltered);
}
}}
>
@@ -483,8 +483,8 @@ const Component = () => {
<Button
onPress={() => {
window.main.trackSegmentEvent({
event: SegmentEvent.importStarted,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.importStarted,
properties: {
source: 'project',
},

View File

@@ -14,10 +14,10 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
const { organizationId, projectId } = params;
invariant(organizationId, 'Organization ID is required');
invariant(projectId, 'Project ID is required');
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const user = await services.userSession.getOrCreate();
const user = await services.userSession.get();
const sessionId = user.id;
invariant(sessionId, 'User must be logged in to delete a project');

View File

@@ -113,7 +113,7 @@ async function getAllLocalFiles({ projectId }: { projectId: string }) {
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { organizationId, projectId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, `Project was not found ${projectId}`);
const organizationProjects =
(await database.find<Project>(models.project.type, {

View File

@@ -11,7 +11,7 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
const projectId = formData.get('projectId');
const workspaceId = formData.get('workspaceId');
invariant(typeof projectId === 'string', 'Project ID is required');
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
invariant(typeof workspaceId === 'string', 'Workspace ID is required');

View File

@@ -14,7 +14,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
invariant(typeof organizationId === 'string', 'Organization ID is required');
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
await services.project.update(project, {

View File

@@ -61,7 +61,7 @@ export async function clientLoader({ params }: Route.ClientLoaderArgs) {
invariant(organizationId, 'Organization ID is required');
if (!models.project.isScratchpadProject({ _id: projectId })) {
const { id: sessionId } = await services.userSession.getOrCreate();
const { id: sessionId } = await services.userSession.get();
if (!sessionId) {
await logout();
@@ -69,7 +69,7 @@ export async function clientLoader({ params }: Route.ClientLoaderArgs) {
}
}
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
if (!project) {
return redirect(href('/organization/:organizationId', { organizationId }));

View File

@@ -6,7 +6,7 @@ import { projectLock } from '~/common/project';
import type { WorkspaceMeta } from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { reportGitProjectCount } from '~/routes/organization.$organizationId.project.new';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { showToast } from '~/ui/components/toast-notification';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -36,13 +36,13 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
const { organizationId, projectId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const effectiveRepoId = models.project.isGitProject(project) ? models.project.getEffectiveRepoId(project) : null;
const gitRepository = effectiveRepoId ? await services.gitRepository.getById(effectiveRepoId) : null;
const user = await services.userSession.getOrCreate();
const user = await services.userSession.get();
const sessionId = user.id;
try {
@@ -106,8 +106,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
sessionId,
});
window.main.trackSegmentEvent({
event: SegmentEvent.projectUpdated,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.projectUpdated,
properties: {
storage: 'local',
},
@@ -158,8 +158,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
name,
});
window.main.trackSegmentEvent({
event: SegmentEvent.projectUpdated,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.projectUpdated,
properties: {
storage: 'remote',
},
@@ -222,8 +222,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
sessionId,
});
window.main.trackSegmentEvent({
event: SegmentEvent.projectUpdated,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.projectUpdated,
properties: {
storage: 'git',
},
@@ -376,8 +376,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
// local project rename
await services.project.update(project, { name });
window.main.trackSegmentEvent({
event: SegmentEvent.projectUpdated,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.projectUpdated,
properties: {
storage: 'local',
},

View File

@@ -19,7 +19,7 @@ export async function clientAction({ params, request }: Route.ClientActionArgs)
return;
}
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found for request');
if (accessLevel === 'project') {
await services.project.update(project, { mcpStdioAccess: true });

View File

@@ -6,12 +6,14 @@ import { v4 as uuidv4 } from 'uuid';
import { getContentDispositionHeader } from '~/common/misc';
import type {
Environment,
Request,
RequestGroup,
RequestMeta,
ResponseInfo,
RunnerResultPerRequestPerIteration,
UserUploadEnvironment,
} from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { database as db, models, services } from '~/insomnia-data';
import type { ResponsePatch } from '~/main/network/libcurl-promise';
import type { TimingStep } from '~/main/network/request-timing';
import {
@@ -25,7 +27,7 @@ import {
tryToInterpolateRequest,
tryToTransformRequestWithPlugins,
} from '~/network/network';
import { type ImportAttribution, importAttributionKey, SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent, type ImportAttribution, importAttributionKey } from '~/ui/analytics';
import { parseGraphQLReqeustBody } from '~/utils/graph-ql';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -356,8 +358,22 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
const activeRequest = await services.request.getById(requestId);
if (activeRequest) {
window.main.trackSegmentEvent({
event: SegmentEvent.requestExecuted,
const [requestAndAncestors, clientCertificates] = await Promise.all([
db.withAncestors<Request | RequestGroup>(activeRequest as Request, [
models.request.type,
models.requestGroup.type,
]),
services.clientCertificate.findByParentId(workspaceId),
]);
const docsWithScripts = requestAndAncestors.filter(
(doc): doc is Request | RequestGroup =>
models.request.isRequest(doc) || models.requestGroup.isRequestGroup(doc),
);
const allPreScripts = docsWithScripts.map(doc => doc.preRequestScript).filter((s): s is string => !!s);
const allPostScripts = docsWithScripts.map(doc => doc.afterResponseScript).filter((s): s is string => !!s);
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.requestExecuted,
properties: {
project_id: projectId,
collection_id: workspaceId,
@@ -371,6 +387,14 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
count_headers: response.headers.length,
count_cookies: response.headers.find(h => h.name === 'set-cookie')?.value.split(',').length || 0,
count_tests: response.requestTestResults?.length || 0,
has_prescript: allPreScripts.length > 0,
has_postscript: allPostScripts.length > 0,
count_prescript_lines: allPreScripts.reduce((sum, s) => sum + s.split('\n').length, 0),
count_postscript_lines: allPostScripts.reduce((sum, s) => sum + s.split('\n').length, 0),
count_query_parameters: activeRequest.parameters?.length ?? 0,
count_path_parameters: activeRequest.pathParameters?.length ?? 0,
has_docs: !!activeRequest.description,
count_certificates: clientCertificates.length,
},
});
@@ -379,8 +403,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
if (jsonImportAttribution) {
try {
const importAttribution = JSON.parse(jsonImportAttribution) as ImportAttribution;
window.main.trackSegmentEvent({
event: SegmentEvent.importedRequestFirstSend,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.importedRequestFirstSend,
properties: {
...importAttribution,
protocol: activeRequest.type,

View File

@@ -2,7 +2,7 @@ import { href } from 'react-router';
import type { WebSocketRequest } from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { updateMimeType } from '~/ui/components/dropdowns/content-type-dropdown';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -45,8 +45,8 @@ export async function clientAction({ params, request }: Route.ClientActionArgs)
await services.helpers.updateRequest(req, patch);
if (req.name !== patch.name) {
window.main.trackSegmentEvent({
event: SegmentEvent.requestRenamed,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.requestRenamed,
});
}

View File

@@ -1,7 +1,7 @@
import { href, redirect } from 'react-router';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -19,8 +19,8 @@ export async function clientAction({ params, request }: Route.ClientActionArgs)
const workspaceMeta = await services.workspaceMeta.getByParentId(workspaceId);
invariant(workspaceMeta, 'Workspace meta not found');
window.main.trackSegmentEvent({
event: SegmentEvent.requestDeleted,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.requestDeleted,
});
if (workspaceMeta.activeRequestId === id) {

View File

@@ -10,7 +10,7 @@ import {
} from '~/common/constants';
import type { Request, RequestBody, RequestParameter } from '~/insomnia-data';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import type { CreateRequestType } from '~/ui/hooks/use-request';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -126,8 +126,8 @@ export async function clientAction({ params, request }: Route.ClientActionArgs)
const certificates = await services.clientCertificate.findByParentId(workspaceId);
window.main.trackSegmentEvent({
event: SegmentEvent.requestCreated,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.requestCreated,
properties: {
requestType,
protocol: requestType,

View File

@@ -32,7 +32,7 @@ import { useRootLoaderData } from '~/root';
import { useOrganizationLoaderData } from '~/routes/organization';
import type { CollectionRunnerContext } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.send';
import { sendActionImplementation } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.send';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { Dropdown, DropdownItem, ItemContent } from '~/ui/components/base/dropdown';
import { ErrorBoundary } from '~/ui/components/error-boundary';
import { HelpTooltip } from '~/ui/components/help-tooltip';
@@ -270,8 +270,8 @@ export const Runner: FC = () => {
}
setIsRunning(true);
window.main.trackSegmentEvent({
event: SegmentEvent.collectionRunExecute,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.collectionRunExecute,
properties: { plan: organizationData?.currentPlan?.type || 'scratchpad', iterations: iterationCount },
});

View File

@@ -58,7 +58,7 @@ import { useRequestNewActionFetcher } from '~/routes/organization.$organizationI
import { useRequestGroupNewActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request-group.new';
import Runner from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.runner';
import { useToggleExpandAllActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.toggle-expand-all';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { DropdownHint } from '~/ui/components/base/dropdown/dropdown-hint';
import { DocumentTab } from '~/ui/components/document-tab';
import { RequestActionsDropdown } from '~/ui/components/dropdowns/request-actions-dropdown';
@@ -138,7 +138,7 @@ export async function clientLoader({ params, request }: Route.ClientLoaderArgs)
if (!params.requestId && !params.requestGroupId) {
const { projectId, workspaceId, organizationId } = params;
const activeProject = await services.project.getById(projectId);
const activeProject = await services.project.get(projectId);
if (!activeProject) {
showResourceNotFoundToast(`Project not found: ${projectId}`);
throw redirect(href('/organization/:organizationId/project', { organizationId }));
@@ -807,8 +807,8 @@ const Debug = () => {
setFilter(value);
if (value.trim() !== '') {
window.main.trackSegmentEvent({
event: SegmentEvent.filterCreatedRequests,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.filterCreatedRequests,
});
}
}}
@@ -829,8 +829,8 @@ const Debug = () => {
selectedKey={sortOrder}
onSelectionChange={order => {
if (order) {
window.main.trackSegmentEvent({
event: SegmentEvent.requestListSortClicked,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.requestListSortClicked,
});
setSearchParams({
...Object.fromEntries(searchParams.entries()),
@@ -884,8 +884,8 @@ const Debug = () => {
defaultSelected={allExpanded}
onChange={() => {
setAllExpanded(!allExpanded);
window.main.trackSegmentEvent({
event: SegmentEvent.requestListExpandCollapseClicked,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.requestListExpandCollapseClicked,
});
toggleExpandAllFetcher.submit({
organizationId,

View File

@@ -1,7 +1,7 @@
import { href } from 'react-router';
import { EnvironmentType, services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -22,8 +22,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
isPrivate,
});
window.main.trackSegmentEvent({
event: SegmentEvent.environmentCreate,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.environmentCreate,
properties: { type: isPrivate ? 'private' : 'global' },
});

View File

@@ -22,7 +22,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
try {
await window.main.sync.takeSnapshot(data.message);
if (data.push) {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
invariant(project.remoteId, 'Project is not remote');

View File

@@ -10,7 +10,7 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
export async function clientAction({ request, params }: Route.ClientActionArgs) {
const { projectId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const formData = await request.formData();

View File

@@ -2,8 +2,8 @@ import { href } from 'react-router';
import { database } from '~/common/database';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { getSyncItems, remoteCompareCache, vcsSegmentEventProperties } from '~/ui/sync-utils';
import { AnalyticsEvent } from '~/ui/analytics';
import { getSyncItems, remoteCompareCache, vcsEventProperties } from '~/ui/sync-utils';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -12,7 +12,7 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
export async function clientAction({ params }: Route.ClientActionArgs) {
const { projectId, workspaceId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const { syncItems } = await getSyncItems({ workspaceId });
try {
@@ -24,9 +24,9 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
projectId: project._id,
});
window.main.trackSegmentEvent({
event: SegmentEvent.vcsAction,
properties: vcsSegmentEventProperties('remote', 'pull'),
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.vcsAction,
properties: vcsEventProperties('remote', 'pull'),
});
// This is to synchronize the local database with the branch changes
await database.batchModifyDocs(delta);
@@ -38,9 +38,9 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error while pulling from remote.';
window.main.trackSegmentEvent({
event: SegmentEvent.vcsAction,
properties: vcsSegmentEventProperties('remote', 'pull', errorMessage),
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.vcsAction,
properties: vcsEventProperties('remote', 'pull', errorMessage),
});
return {

View File

@@ -1,8 +1,8 @@
import { href } from 'react-router';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { remoteCompareCache, vcsSegmentEventProperties } from '~/ui/sync-utils';
import { AnalyticsEvent } from '~/ui/analytics';
import { remoteCompareCache, vcsEventProperties } from '~/ui/sync-utils';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -11,7 +11,7 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
export async function clientAction({ params }: Route.ClientActionArgs) {
const { projectId, workspaceId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
invariant(project.remoteId, 'Project is not remote');
@@ -21,9 +21,9 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
teamProjectId: project.remoteId,
});
window.main.trackSegmentEvent({
event: SegmentEvent.vcsAction,
properties: vcsSegmentEventProperties('remote', 'push'),
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.vcsAction,
properties: vcsEventProperties('remote', 'push'),
});
delete remoteCompareCache[workspaceId];
@@ -34,9 +34,9 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error while pushing to remote.';
window.main.trackSegmentEvent({
event: SegmentEvent.vcsAction,
properties: vcsSegmentEventProperties('remote', 'push', errorMessage),
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.vcsAction,
properties: vcsEventProperties('remote', 'push', errorMessage),
});
return {

View File

@@ -10,7 +10,7 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { projectId, workspaceId } = params;
try {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
invariant(project.remoteId, 'Project is not remote');
const { syncItems } = await getSyncItems({ workspaceId });
@@ -68,7 +68,7 @@ export async function clientLoader({ params }: Route.ClientLoaderArgs) {
export async function clientAction({ params }: Route.ClientActionArgs) {
const { projectId, workspaceId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
invariant(project.remoteId, 'Project is not remote');

View File

@@ -13,7 +13,7 @@ export async function clientLoader({ params }: Route.ClientLoaderArgs) {
invariant(typeof projectId === 'string', 'Project Id is required');
try {
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const remoteId = project.remoteId;

View File

@@ -14,7 +14,7 @@ import { useWorkspaceLoaderData } from './organization.$organizationId.project.$
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { projectId, workspaceId, organizationId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
if (!project) {
showResourceNotFoundToast(`Project not found: ${projectId}`);
throw redirect(href('/organization/:organizationId/project', { organizationId }));

View File

@@ -2,7 +2,7 @@ import { href, redirect } from 'react-router';
import { getMockServiceBinURL } from '~/common/constants';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -11,7 +11,7 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
export async function clientAction({ params }: Route.ClientActionArgs) {
const { organizationId, projectId, workspaceId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const workspace = await services.workspace.getById(workspaceId);
@@ -62,8 +62,8 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
});
}
window.main.trackSegmentEvent({
event: SegmentEvent.generateCollectionFromMock,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.generateCollectionFromMock,
properties: {
count_requests: mockRoutes.length,
},

View File

@@ -1,7 +1,7 @@
import { href, redirect } from 'react-router';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -16,8 +16,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
await services.mockRoute.remove(mockRoute);
window.main.trackSegmentEvent({
event: SegmentEvent.mockRouteDelete,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.mockRouteDelete,
});
if (isSelected) {

View File

@@ -22,7 +22,7 @@ import { models, services } from '~/insomnia-data';
import { useRootLoaderData } from '~/root';
import { useRequestNewMockSendActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.new-mock-send';
import { useMockRouteUpdateActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mock-server.mock-route.$mockRouteId.update';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { CodeEditor } from '~/ui/components/.client/codemirror/code-editor';
import { Dropdown, DropdownItem, ItemContent } from '~/ui/components/base/dropdown';
import { MockResponseHeadersEditor } from '~/ui/components/editors/mock-response-headers-editor';
@@ -304,8 +304,8 @@ export const MockRouteRoute = () => {
noLint={mockRoute.body?.includes('{{') && mockRoute.body?.includes('}}')}
updateFilter={filter => {
if (filter) {
window.main.trackSegmentEvent({
event: SegmentEvent.filterCreatedResponseBody,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.filterCreatedResponseBody,
});
}
}}

View File

@@ -2,7 +2,7 @@ import { href } from 'react-router';
import type { MockRoute } from '~/insomnia-data';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -47,8 +47,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
await services.mockRoute.update(mockRoute, patch);
window.main.trackSegmentEvent({
event: SegmentEvent.mockRouteEdit,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.mockRouteEdit,
});
return null;

View File

@@ -2,7 +2,7 @@ import { href, redirect } from 'react-router';
import type { MockRoute } from '~/insomnia-data';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -57,8 +57,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
delete patch.mockServerName;
const mockRoute = await services.mockRoute.create({ ...patch, parentId: newMockServer._id });
window.main.trackSegmentEvent({
event: SegmentEvent.mockRouteCreate,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.mockRouteCreate,
});
return redirect(
@@ -78,8 +78,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
invariant(mockServer, 'Mock server not found');
const mockRoute = await services.mockRoute.create(patch);
window.main.trackSegmentEvent({
event: SegmentEvent.mockRouteCreate,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.mockRouteCreate,
properties: {
source: 'from_response',
},

View File

@@ -49,7 +49,7 @@ export interface MockServerLoaderData {
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { workspaceId, projectId, organizationId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
if (!project) {
showResourceNotFoundToast(`Project not found: ${projectId}`);
throw redirect(href('/organization/:organizationId/project', { organizationId }));

View File

@@ -3,7 +3,7 @@ import { href, redirect } from 'react-router';
import { importResourcesToWorkspace, scanResources } from '~/common/import';
import { models, services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -12,7 +12,7 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
export async function clientAction({ params }: Route.ClientActionArgs) {
const { organizationId, projectId, workspaceId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const apiSpec = await services.apiSpec.getByParentId(workspaceId);
@@ -53,8 +53,8 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
workspaceId,
});
window.main.trackSegmentEvent({
event: SegmentEvent.generateCollection,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.generateCollection,
properties: {
count_requests: scannedResources.map(r => r.requests?.length ?? 0).reduce((a, b) => a + b, 0),
},

View File

@@ -35,7 +35,7 @@ import {
import { useSpecGenerateRequestCollectionActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.spec.generate-request-collection';
import { useSpecUpdateActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.spec.update';
import { useStorageRulesLoaderFetcher } from '~/routes/organization.$organizationId.storage-rules';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { CodeEditor, type CodeEditorHandle } from '~/ui/components/.client/codemirror/code-editor';
import { DesignEmptyState } from '~/ui/components/design-empty-state';
import { DocumentTab } from '~/ui/components/document-tab';
@@ -60,7 +60,7 @@ import type { Route } from './+types/organization.$organizationId.project.$proje
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { organizationId, projectId, workspaceId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
if (!project) {
showResourceNotFoundToast(`Project not found: ${projectId}`);
throw redirect(href('/organization/:organizationId/project', { organizationId }));
@@ -402,8 +402,8 @@ const Component = ({ params }: Route.ComponentProps) => {
name: 'Toggle preview',
icon: <Icon className="w-3" icon={isSpecPaneOpen ? 'eye' : 'eye-slash'} />,
action: () => {
window.main.trackSegmentEvent({
event: SegmentEvent.designerPreviewToggled,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.designerPreviewToggled,
properties: {
status: !isSpecPaneOpen ? 'open' : 'collapsed',
},
@@ -492,8 +492,8 @@ const Component = ({ params }: Route.ComponentProps) => {
{isGenerateMockServersWithAIEnabled && (
<Button
onPress={() => {
window.main.trackSegmentEvent({
event: SegmentEvent.designerGenerateMockClicked,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.designerGenerateMockClicked,
});
setNewMockServerModalOpen(true);
}}
@@ -510,8 +510,8 @@ const Component = ({ params }: Route.ComponentProps) => {
className="flex h-full items-center justify-center gap-2 rounded-xs px-2 text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
onChange={value => {
setIsSpecPaneOpen(value);
window.main.trackSegmentEvent({
event: SegmentEvent.designerPreviewToggled,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.designerPreviewToggled,
properties: {
status: !value ? 'open' : 'collapsed',
},

View File

@@ -1,7 +1,7 @@
import { href, redirect } from 'react-router';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -16,7 +16,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
await services.unitTestSuite.remove(unitTestSuite);
window.main.trackSegmentEvent({ event: SegmentEvent.testSuiteDelete });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.testSuiteDelete });
return redirect(
href(`/organization/:organizationId/project/:projectId/workspace/:workspaceId/test`, {

View File

@@ -5,7 +5,7 @@ import { database } from '~/common/database';
import type { UnitTest } from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { getSendRequestCallback } from '~/network/unit-test-feature';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -52,7 +52,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
results,
parentId: workspaceId,
});
window.main.trackSegmentEvent({ event: SegmentEvent.unitTestRunAll, properties: { organizationId, projectId } });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.unitTestRunAll, properties: { organizationId, projectId } });
return redirect(
href(
@@ -93,7 +93,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
results,
parentId: workspaceId,
});
window.main.trackSegmentEvent({ event: SegmentEvent.unitTestRunAll, properties: { organizationId, projectId } });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.unitTestRunAll, properties: { organizationId, projectId } });
return redirect(
href(

View File

@@ -3,7 +3,7 @@ import { href } from 'react-router';
import { database } from '~/common/database';
import type { UnitTest } from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -18,7 +18,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
invariant(unitTest, 'Test not found');
await services.unitTest.remove(unitTest);
window.main.trackSegmentEvent({ event: SegmentEvent.unitTestDelete });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.unitTestDelete });
return null;
}

View File

@@ -5,7 +5,7 @@ import { database } from '~/common/database';
import type { UnitTest } from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { getSendRequestCallback } from '~/network/unit-test-feature';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -53,7 +53,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
results,
parentId: unitTest.parentId,
});
window.main.trackSegmentEvent({ event: SegmentEvent.unitTestRun, properties: { organizationId, projectId } });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.unitTestRun, properties: { organizationId, projectId } });
return redirect(
href(
@@ -94,7 +94,7 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
results,
parentId: unitTest.parentId,
});
window.main.trackSegmentEvent({ event: SegmentEvent.unitTestRun, properties: { organizationId, projectId } });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.unitTestRun, properties: { organizationId, projectId } });
return redirect(
href(

View File

@@ -1,7 +1,7 @@
import { href } from 'react-router';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -22,7 +22,7 @@ expect(response1.status).to.equal(200);`,
name,
});
window.main.trackSegmentEvent({ event: SegmentEvent.unitTestCreate });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.unitTestCreate });
return null;
}

View File

@@ -1,7 +1,7 @@
import { href, redirect } from 'react-router';
import { services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -19,7 +19,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
name,
});
window.main.trackSegmentEvent({ event: SegmentEvent.testSuiteCreate });
window.main.trackAnalyticsEvent({ event: AnalyticsEvent.testSuiteCreate });
return redirect(
href('/organization/:organizationId/project/:projectId/workspace/:workspaceId/test/test-suite/:testSuiteId', {

View File

@@ -88,7 +88,7 @@ const workspaceFileIssueModalText = {
export async function clientLoader({ params, request }: Route.ClientLoaderArgs) {
const { organizationId, projectId, workspaceId } = params;
const activeProject = await services.project.getById(projectId);
const activeProject = await services.project.get(projectId);
if (!activeProject) {
showResourceNotFoundToast(`Project not found: ${projectId}`);
throw redirect(href('/organization/:organizationId/project', { organizationId }));
@@ -281,7 +281,7 @@ export async function clientLoader({ params, request }: Route.ClientLoaderArgs)
return collection;
}
const userSession = await services.userSession.getOrCreate();
const userSession = await services.userSession.get();
const isLoggedInIsCloudProjectAndIsNotGitRepo = userSession.id && activeProject.remoteId && !gitRepository;
let vcsVersion = null;
if (isLoggedInIsCloudProjectAndIsNotGitRepo) {

View File

@@ -2,7 +2,7 @@ import { href, redirect } from 'react-router';
import type { Project, Workspace } from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { SegmentEvent } from '~/ui/analytics';
import { AnalyticsEvent } from '~/ui/analytics';
import uiEventBus, { CLOUD_SYNC_FILE_CHANGE } from '~/ui/event-bus';
import { invariant } from '~/utils/invariant';
import { createFetcherSubmitHook } from '~/utils/router';
@@ -52,8 +52,8 @@ async function deleteWorkspace(workspace: Workspace | null, project: Project | n
await deleteWorkspaceFromLocal(workspace);
if (workspace.scope === 'mock-server') {
window.main.trackSegmentEvent({
event: SegmentEvent.mockDelete,
window.main.trackAnalyticsEvent({
event: AnalyticsEvent.mockDelete,
});
}
@@ -63,7 +63,7 @@ async function deleteWorkspace(workspace: Workspace | null, project: Project | n
export async function clientAction({ request, params }: Route.ClientActionArgs) {
const { organizationId, projectId } = params;
const project = await services.project.getById(projectId);
const project = await services.project.get(projectId);
invariant(project, 'Project not found');
const formData = await request.formData();

Some files were not shown because too many files have changed in this diff Show More