diff --git a/package-lock.json b/package-lock.json index c3b022b40..8f5586b3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@mongodb-js/compass-components": "^1.34.8", "@mongodb-js/connection-form": "1.47.8", "@mongodb-js/connection-info": "^0.11.9", + "@mongodb-js/device-id": "^0.2.0", "@mongodb-js/mongodb-constants": "^0.11.1", "@mongosh/browser-runtime-electron": "^3.10.0", "@mongosh/i18n": "^2.9.1", @@ -35,6 +36,7 @@ "mongodb-log-writer": "^2.4.1", "mongodb-query-parser": "^4.3.2", "mongodb-schema": "^12.6.2", + "node-machine-id": "1.1.12", "numeral": "^2.0.6", "query-string": "^7.1.3", "react": "^18.3.1", @@ -7539,6 +7541,12 @@ "mongodb-data-service": "^22.25.8" } }, + "node_modules/@mongodb-js/device-id": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/device-id/-/device-id-0.2.0.tgz", + "integrity": "sha512-auEMkQc6hpSQSQziK5AbeuJeVnI7OQvWmaoMIWcXrMm+RA6pF0ADXZPS6kBtBIrRhWElV6PVYiq+Gfzsss2RYQ==", + "license": "Apache-2.0" + }, "node_modules/@mongodb-js/devtools-connect": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/@mongodb-js/devtools-connect/-/devtools-connect-3.7.2.tgz", @@ -21410,6 +21418,12 @@ "integrity": "sha512-WlUacqHqHWb383Mec656pJmAtg7EXBcijg1sovgy0tI4xevAzv8RqcRARTMlzm0HLXzy1Pdo/DMU19/fxoMnmA==", "dev": true }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", diff --git a/package.json b/package.json index e430745ca..81ef93daa 100644 --- a/package.json +++ b/package.json @@ -1318,6 +1318,7 @@ "@mongodb-js/compass-components": "^1.34.8", "@mongodb-js/connection-form": "1.47.8", "@mongodb-js/connection-info": "^0.11.9", + "@mongodb-js/device-id": "^0.2.0", "@mongodb-js/mongodb-constants": "^0.11.1", "@mongosh/browser-runtime-electron": "^3.10.0", "@mongosh/i18n": "^2.9.1", @@ -1338,6 +1339,7 @@ "mongodb-log-writer": "^2.4.1", "mongodb-query-parser": "^4.3.2", "mongodb-schema": "^12.6.2", + "node-machine-id": "1.1.12", "numeral": "^2.0.6", "query-string": "^7.1.3", "react": "^18.3.1", diff --git a/src/explorer/helpTree.ts b/src/explorer/helpTree.ts index ca0f5bc94..10611119a 100644 --- a/src/explorer/helpTree.ts +++ b/src/explorer/helpTree.ts @@ -116,8 +116,7 @@ export default class HelpTree iconName: 'report', }); - const telemetryUserIdentity = - this._telemetryService?.getTelemetryUserIdentity(); + const telemetryUserIdentity = this._telemetryService?.userIdentity; const atlas = new HelpLinkTreeItem({ title: 'Create Free Atlas Cluster', diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 2a82a2efb..201a6e957 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -180,7 +180,7 @@ export default class MDBExtensionController implements vscode.Disposable { this._explorerController.activateConnectionsTreeView(); this._helpExplorer.activateHelpTreeView(); this._playgroundsExplorer.activatePlaygroundsTreeView(); - this._telemetryService.activateSegmentAnalytics(); + void this._telemetryService.activateSegmentAnalytics(); this._participantController.createParticipant(this._context); await this._connectionController.loadSavedConnections(); diff --git a/src/telemetry/telemetryService.ts b/src/telemetry/telemetryService.ts index aa228dbf3..7a40e3dcc 100644 --- a/src/telemetry/telemetryService.ts +++ b/src/telemetry/telemetryService.ts @@ -2,7 +2,7 @@ import path from 'path'; import * as vscode from 'vscode'; import { config } from 'dotenv'; import type { DataService } from 'mongodb-data-service'; -import fs from 'fs'; +import fs from 'fs/promises'; import { Analytics as SegmentAnalytics } from '@segment/analytics-node'; import { throttle } from 'lodash'; @@ -18,6 +18,10 @@ import { SidePanelOpenedTelemetryEvent, ParticipantResponseFailedTelemetryEvent, } from './telemetryEvents'; +import { getDeviceId } from '@mongodb-js/device-id'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const nodeMachineId = require('node-machine-id'); const log = createLogger('telemetry'); // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -26,6 +30,7 @@ const { version } = require('../../package.json'); export type SegmentProperties = { event: string; anonymousId: string; + deviceId?: string; properties: Record; }; @@ -33,12 +38,17 @@ export type SegmentProperties = { * This controller manages telemetry. */ export class TelemetryService { - _segmentAnalytics?: SegmentAnalytics; - _segmentAnonymousId: string; - _segmentKey?: string; // The segment API write key. - - private _context: vscode.ExtensionContext; - private _shouldTrackTelemetry: boolean; // When tests run the extension, we don't want to track telemetry. + private _segmentAnalytics?: SegmentAnalytics; + public _segmentKey?: string; // The segment API write key. + private eventBuffer: TelemetryEvent[] = []; + private isBufferingEvents = true; + public userIdentity: { + anonymousId: string; + deviceId?: string; + }; + private resolveDeviceId: ((value: string) => void) | undefined; + private readonly _context: vscode.ExtensionContext; + private readonly _shouldTrackTelemetry: boolean; // When tests run the extension, we don't want to track telemetry. constructor( storageController: StorageController, @@ -48,11 +58,12 @@ export class TelemetryService { const { anonymousId } = storageController.getUserIdentity(); this._context = context; this._shouldTrackTelemetry = shouldTrackTelemetry || false; - this._segmentAnonymousId = anonymousId; - this._segmentKey = this._readSegmentKey(); + this.userIdentity = { + anonymousId, + }; } - private _readSegmentKey(): string | undefined { + private async readSegmentKey(): Promise { config({ path: path.join(this._context.extensionPath, '.env') }); try { @@ -61,18 +72,21 @@ export class TelemetryService { './constants.json' ); // eslint-disable-next-line no-sync - const constantsFile = fs.readFileSync(segmentKeyFileLocation, 'utf8'); + const constantsFile = await fs.readFile(segmentKeyFileLocation, { + encoding: 'utf8', + }); const { segmentKey } = JSON.parse(constantsFile) as { segmentKey?: string; }; return segmentKey; } catch (error) { log.error('Failed to read segmentKey from the constants file', error); - return; + return undefined; } } - activateSegmentAnalytics(): void { + async activateSegmentAnalytics(): Promise { + this._segmentKey = await this.readSegmentKey(); if (!this._segmentKey) { return; } @@ -83,12 +97,20 @@ export class TelemetryService { flushInterval: 10000, // 10 seconds is the default libraries' value. }); - const segmentProperties = this.getTelemetryUserIdentity(); - this._segmentAnalytics.identify(segmentProperties); - log.info('Segment analytics activated', segmentProperties); + this.userIdentity = await this.getTelemetryUserIdentity(); + this._segmentAnalytics.identify(this.userIdentity); + this.isBufferingEvents = false; + log.info('Segment analytics activated', this.userIdentity); + + // Process buffered events + let event: TelemetryEvent | undefined; + while ((event = this.eventBuffer.shift())) { + this.track(event); + } } deactivate(): void { + this.resolveDeviceId?.('unknown'); // Flush on demand to make sure that nothing is left in the queue. void this._segmentAnalytics?.closeAndFlush(); } @@ -130,8 +152,13 @@ export class TelemetryService { track(event: TelemetryEvent): void { try { + if (this.isBufferingEvents) { + this.eventBuffer.push(event); + return; + } + this._segmentAnalyticsTrack({ - ...this.getTelemetryUserIdentity(), + ...this.userIdentity, event: event.type, properties: { ...event.properties, @@ -153,9 +180,17 @@ export class TelemetryService { this.track(new NewConnectionTelemetryEvent(connectionTelemetryProperties)); } - getTelemetryUserIdentity(): { anonymousId: string } { + private async getTelemetryUserIdentity(): Promise { + const { value: deviceId, resolve: resolveDeviceId } = getDeviceId({ + getMachineId: (): Promise => nodeMachineId.machineId(true), + isNodeMachineId: true, + }); + + this.resolveDeviceId = resolveDeviceId; + return { - anonymousId: this._segmentAnonymousId, + anonymousId: this.userIdentity.anonymousId, + deviceId: await deviceId, }; } diff --git a/src/test/suite/explorer/helpExplorer.test.ts b/src/test/suite/explorer/helpExplorer.test.ts index 4a58cd6f5..b8e67c509 100644 --- a/src/test/suite/explorer/helpExplorer.test.ts +++ b/src/test/suite/explorer/helpExplorer.test.ts @@ -42,7 +42,7 @@ suite('Help Explorer Test Suite', function () { assert.strictEqual(atlasHelpItem.label, 'Create Free Atlas Cluster'); assert.strictEqual(atlasHelpItem.url.includes('mongodb.com'), true); const { anonymousId } = - mdbTestExtension.testExtensionController._telemetryService.getTelemetryUserIdentity(); + mdbTestExtension.testExtensionController._telemetryService.userIdentity; assert.strictEqual( new URL(atlasHelpItem.url).searchParams.get('ajs_aid'), anonymousId diff --git a/src/test/suite/telemetry/telemetryService.test.ts b/src/test/suite/telemetry/telemetryService.test.ts index 408fd2455..fd2964555 100644 --- a/src/test/suite/telemetry/telemetryService.test.ts +++ b/src/test/suite/telemetry/telemetryService.test.ts @@ -38,8 +38,12 @@ config({ path: resolve(__dirname, '../../../../.env') }); suite('Telemetry Controller Test Suite', () => { const testTelemetryService = mdbTestExtension.testExtensionController._telemetryService; + let dataServiceStub: DataService; - const { anonymousId } = testTelemetryService.getTelemetryUserIdentity(); + const telemetryIdentity = { + ...testTelemetryService.userIdentity, + deviceId: 'testDeviceId', + }; let fakeSegmentAnalyticsTrack: SinonSpy; @@ -63,6 +67,14 @@ suite('Telemetry Controller Test Suite', () => { '_segmentAnalyticsTrack', fakeSegmentAnalyticsTrack ); + sandbox.replace( + mdbTestExtension.testExtensionController._telemetryService, + // @ts-expect-error This is a private method + 'getTelemetryUserIdentity', + () => { + return Promise.resolve(telemetryIdentity); + } + ); sandbox.replace( mdbTestExtension.testExtensionController._playgroundController ._languageServerController, @@ -117,232 +129,433 @@ suite('Telemetry Controller Test Suite', () => { expect(testTelemetryService._segmentKey).to.be.a('string'); }); - test('track command run event', async () => { - await vscode.commands.executeCommand('mdb.addConnection'); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Command Run', - properties: { - command: 'mdb.addConnection', - extension_version: version, - }, - }) - ); - }); + suite('after setup is complete', () => { + beforeEach(async () => { + await testTelemetryService.activateSegmentAnalytics(); + }); - test('track new connection event when connecting via connection string', async () => { - await testTelemetryService.trackNewConnection( - dataServiceStub, - ConnectionTypes.CONNECTION_STRING - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'New Connection', - properties: { - is_used_connect_screen: false, - is_used_command_palette: true, - is_used_saved_connection: false, - vscode_mdb_extension_version: version, - extension_version: version, - }, - }) - ); - }); + test('track command run event', async () => { + await vscode.commands.executeCommand('mdb.addConnection'); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Command Run', + properties: { + command: 'mdb.addConnection', + extension_version: version, + }, + }) + ); + }); - test('track new connection event when connecting via connection form', async () => { - await testTelemetryService.trackNewConnection( - dataServiceStub, - ConnectionTypes.CONNECTION_FORM - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'New Connection', - properties: { - is_used_connect_screen: true, - is_used_command_palette: false, - is_used_saved_connection: false, - vscode_mdb_extension_version: version, - extension_version: version, - }, - }) - ); - }); + test('track new connection event when connecting via connection string', async () => { + await testTelemetryService.trackNewConnection( + dataServiceStub, + ConnectionTypes.CONNECTION_STRING + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'New Connection', + properties: { + is_used_connect_screen: false, + is_used_command_palette: true, + is_used_saved_connection: false, + vscode_mdb_extension_version: version, + extension_version: version, + }, + }) + ); + }); - test('track new connection event when connecting via saved connection', async () => { - await testTelemetryService.trackNewConnection( - dataServiceStub, - ConnectionTypes.CONNECTION_ID - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'New Connection', - properties: { - is_used_connect_screen: false, - is_used_command_palette: false, - is_used_saved_connection: true, - vscode_mdb_extension_version: version, - extension_version: version, - }, - }) - ); - }); + test('track new connection event when connecting via connection form', async () => { + await testTelemetryService.trackNewConnection( + dataServiceStub, + ConnectionTypes.CONNECTION_FORM + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'New Connection', + properties: { + is_used_connect_screen: true, + is_used_command_palette: false, + is_used_saved_connection: false, + vscode_mdb_extension_version: version, + extension_version: version, + }, + }) + ); + }); - test('track document saved form a tree-view event', () => { - const source = DocumentSource.DOCUMENT_SOURCE_TREEVIEW; - testTelemetryService.track(new DocumentUpdatedTelemetryEvent(source, true)); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Document Updated', - properties: { - source: 'treeview', - success: true, - extension_version: version, - }, - }) - ); - }); + test('track new connection event when connecting via saved connection', async () => { + await testTelemetryService.trackNewConnection( + dataServiceStub, + ConnectionTypes.CONNECTION_ID + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'New Connection', + properties: { + is_used_connect_screen: false, + is_used_command_palette: false, + is_used_saved_connection: true, + vscode_mdb_extension_version: version, + extension_version: version, + }, + }) + ); + }); - test('track document opened form playground results', () => { - const source = DocumentSource.DOCUMENT_SOURCE_PLAYGROUND; - testTelemetryService.track(new DocumentEditedTelemetryEvent(source)); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Document Edited', - properties: { source: 'playground', extension_version: version }, - }) - ); - }); + test('track document saved form a tree-view event', () => { + const source = DocumentSource.DOCUMENT_SOURCE_TREEVIEW; + testTelemetryService.track( + new DocumentUpdatedTelemetryEvent(source, true) + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Document Updated', + properties: { + source: 'treeview', + success: true, + extension_version: version, + }, + }) + ); + }); - test('track playground code executed event', async () => { - const testPlaygroundController = - mdbTestExtension.testExtensionController._playgroundController; - const source = new vscode.CancellationTokenSource(); - await testPlaygroundController._evaluate( - { - codeToEvaluate: 'show dbs', - }, - source.token - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Code Executed', - properties: { - type: 'other', - partial: false, - error: false, - extension_version: version, - }, - }) - ); - }); + test('track document opened form playground results', () => { + const source = DocumentSource.DOCUMENT_SOURCE_PLAYGROUND; + testTelemetryService.track(new DocumentEditedTelemetryEvent(source)); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Document Edited', + properties: { source: 'playground', extension_version: version }, + }) + ); + }); - // TODO: re-enable two tests after https://jira.mongodb.org/browse/VSCODE-432 - test.skip('track mongodb playground loaded event', async () => { - const docPath = path.resolve( - __dirname, - '../../../../src/test/fixture/testPlayground.mongodb' - ); - await vscode.workspace.openTextDocument(vscode.Uri.file(docPath)); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Loaded', - properties: { - file_type: 'mongodb', - extension_version: version, + test('track playground code executed event', async () => { + const testPlaygroundController = + mdbTestExtension.testExtensionController._playgroundController; + const source = new vscode.CancellationTokenSource(); + await testPlaygroundController._evaluate( + { + codeToEvaluate: 'show dbs', }, - }) - ); - }); + source.token + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Code Executed', + properties: { + type: 'other', + partial: false, + error: false, + extension_version: version, + }, + }) + ); + }); - test.skip('track mongodbjs playground loaded event', async () => { - const docPath = path.resolve( - __dirname, - '../../../../src/test/fixture/testPlayground.mongodb.js' - ); - await vscode.workspace.openTextDocument(vscode.Uri.file(docPath)); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Loaded', - properties: { - file_type: 'mongodbjs', - extension_version: version, - }, - }) - ); - }); + // TODO: re-enable two tests after https://jira.mongodb.org/browse/VSCODE-432 + test.skip('track mongodb playground loaded event', async () => { + const docPath = path.resolve( + __dirname, + '../../../../src/test/fixture/testPlayground.mongodb' + ); + await vscode.workspace.openTextDocument(vscode.Uri.file(docPath)); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Loaded', + properties: { + file_type: 'mongodb', + extension_version: version, + }, + }) + ); + }); - test('track playground saved event', () => { - testTelemetryService.track( - new PlaygroundSavedTelemetryEvent( - vscode.Uri.file('/users/peter/projects/test/myplayground.mongodb.js') - ) - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Saved', - properties: { - file_type: 'mongodbjs', - extension_version: version, - }, - }) - ); - }); + test.skip('track mongodbjs playground loaded event', async () => { + const docPath = path.resolve( + __dirname, + '../../../../src/test/fixture/testPlayground.mongodb.js' + ); + await vscode.workspace.openTextDocument(vscode.Uri.file(docPath)); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Loaded', + properties: { + file_type: 'mongodbjs', + extension_version: version, + }, + }) + ); + }); - test('track link clicked event', () => { - testTelemetryService.track( - new LinkClickedTelemetryEvent('helpPanel', 'linkId') - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Link Clicked', - properties: { - screen: 'helpPanel', - link_id: 'linkId', - extension_version: version, - }, - }) - ); - }); + test('track playground saved event', () => { + testTelemetryService.track( + new PlaygroundSavedTelemetryEvent( + vscode.Uri.file('/users/peter/projects/test/myplayground.mongodb.js') + ) + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Saved', + properties: { + file_type: 'mongodbjs', + extension_version: version, + }, + }) + ); + }); - test('track playground exported to language', () => { - testTelemetryService.track( - new PlaygroundExportedToLanguageTelemetryEvent('java', 3, false) - ); + test('track link clicked event', () => { + testTelemetryService.track( + new LinkClickedTelemetryEvent('helpPanel', 'linkId') + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Link Clicked', + properties: { + screen: 'helpPanel', + link_id: 'linkId', + extension_version: version, + }, + }) + ); + }); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Exported To Language', - properties: { - language: 'java', - with_driver_syntax: false, - extension_version: version, - }, - }) - ); + test('track playground exported to language', () => { + testTelemetryService.track( + new PlaygroundExportedToLanguageTelemetryEvent('java', 3, false) + ); + + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Exported To Language', + properties: { + language: 'java', + with_driver_syntax: false, + extension_version: version, + }, + }) + ); + }); + + suite('playground created', () => { + test('track on search for documents', async () => { + await vscode.commands.executeCommand('mdb.searchForDocuments', { + databaseName: 'databaseName', + collectionName: 'collectionName', + }); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'search', + extension_version: version, + }, + }) + ); + }); + + test('track on create collection', async () => { + const testDatabaseTreeItem = new DatabaseTreeItem({ + databaseName: 'databaseName', + dataService: new DataServiceStub() as unknown as DataService, + isExpanded: false, + cacheIsUpToDate: false, + childrenCache: {}, + }); + await vscode.commands.executeCommand( + 'mdb.addCollection', + testDatabaseTreeItem + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'createCollection', + extension_version: version, + }, + }) + ); + }); + + test('track on create database', async () => { + await vscode.commands.executeCommand('mdb.addDatabase', { + connectionId: 'testconnectionId', + }); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'createDatabase', + extension_version: version, + }, + }) + ); + }); + + test('track on create index', async () => { + await vscode.commands.executeCommand('mdb.createIndexFromTreeView', { + databaseName: 'databaseName', + collectionName: 'collectionName', + }); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'index', + extension_version: version, + }, + }) + ); + }); + + test('track on clone document', async () => { + const mockDocument = { + _id: 'pancakes', + name: '', + time: { + $time: '12345', + }, + }; + const dataServiceStub = { + find: () => { + return Promise.resolve([mockDocument]); + }, + } as unknown as DataService; + const documentItem = new DocumentTreeItem({ + document: mockDocument, + namespace: 'waffle.house', + documentIndexInTree: 0, + dataService: dataServiceStub, + resetDocumentListCache: (): Promise => Promise.resolve(), + }); + await vscode.commands.executeCommand( + 'mdb.cloneDocumentFromTreeView', + documentItem + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'cloneDocument', + extension_version: version, + }, + }) + ); + }); + + test('track on crud from the command palette', async () => { + await vscode.commands.executeCommand('mdb.createPlayground'); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'crud', + extension_version: version, + }, + }) + ); + }); + + test('track on crud from overview page', async () => { + await vscode.commands.executeCommand( + 'mdb.createNewPlaygroundFromOverviewPage' + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'crud', + extension_version: version, + }, + }) + ); + }); + + test('track on crud from tree view', async () => { + await vscode.commands.executeCommand( + 'mdb.createNewPlaygroundFromTreeView' + ); + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Playground Created', + properties: { + playground_type: 'crud', + extension_version: version, + }, + }) + ); + }); + }); + + test.skip('track saved connections loaded', () => { + testTelemetryService.track( + new SavedConnectionsLoadedTelemetryEvent({ + savedConnections: 3, + loadedConnections: 3, + presetConnections: 3, + connectionsWithSecretsInKeytar: 0, + connectionsWithSecretsInSecretStorage: 3, + }) + ); + + sandbox.assert.calledWith( + fakeSegmentAnalyticsTrack, + sinon.match({ + ...telemetryIdentity, + event: 'Saved Connections Loaded', + properties: { + saved_connections: 3, + loaded_connections: 3, + preset_connections: 3, + connections_with_secrets_in_keytar: 0, + connections_with_secrets_in_secret_storage: 3, + }, + }) + ); + }); }); suite('prepare playground result types', () => { @@ -431,199 +644,6 @@ suite('Telemetry Controller Test Suite', () => { }); }); - suite('playground created', () => { - test('track on search for documents', async () => { - await vscode.commands.executeCommand('mdb.searchForDocuments', { - databaseName: 'databaseName', - collectionName: 'collectionName', - }); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'search', - extension_version: version, - }, - }) - ); - }); - - test('track on create collection', async () => { - const testDatabaseTreeItem = new DatabaseTreeItem({ - databaseName: 'databaseName', - dataService: new DataServiceStub() as unknown as DataService, - isExpanded: false, - cacheIsUpToDate: false, - childrenCache: {}, - }); - await vscode.commands.executeCommand( - 'mdb.addCollection', - testDatabaseTreeItem - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'createCollection', - extension_version: version, - }, - }) - ); - }); - - test('track on create database', async () => { - await vscode.commands.executeCommand('mdb.addDatabase', { - connectionId: 'testconnectionId', - }); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'createDatabase', - extension_version: version, - }, - }) - ); - }); - - test('track on create index', async () => { - await vscode.commands.executeCommand('mdb.createIndexFromTreeView', { - databaseName: 'databaseName', - collectionName: 'collectionName', - }); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'index', - extension_version: version, - }, - }) - ); - }); - - test('track on clone document', async () => { - const mockDocument = { - _id: 'pancakes', - name: '', - time: { - $time: '12345', - }, - }; - const dataServiceStub = { - find: () => { - return Promise.resolve([mockDocument]); - }, - } as unknown as DataService; - const documentItem = new DocumentTreeItem({ - document: mockDocument, - namespace: 'waffle.house', - documentIndexInTree: 0, - dataService: dataServiceStub, - resetDocumentListCache: (): Promise => Promise.resolve(), - }); - await vscode.commands.executeCommand( - 'mdb.cloneDocumentFromTreeView', - documentItem - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'cloneDocument', - extension_version: version, - }, - }) - ); - }); - - test('track on crud from the command palette', async () => { - await vscode.commands.executeCommand('mdb.createPlayground'); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'crud', - extension_version: version, - }, - }) - ); - }); - - test('track on crud from overview page', async () => { - await vscode.commands.executeCommand( - 'mdb.createNewPlaygroundFromOverviewPage' - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'crud', - extension_version: version, - }, - }) - ); - }); - - test('track on crud from tree view', async () => { - await vscode.commands.executeCommand( - 'mdb.createNewPlaygroundFromTreeView' - ); - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Playground Created', - properties: { - playground_type: 'crud', - extension_version: version, - }, - }) - ); - }); - }); - - test.skip('track saved connections loaded', () => { - testTelemetryService.track( - new SavedConnectionsLoadedTelemetryEvent({ - savedConnections: 3, - loadedConnections: 3, - presetConnections: 3, - connectionsWithSecretsInKeytar: 0, - connectionsWithSecretsInSecretStorage: 3, - }) - ); - - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Saved Connections Loaded', - properties: { - saved_connections: 3, - loaded_connections: 3, - preset_connections: 3, - connections_with_secrets_in_keytar: 0, - connections_with_secrets_in_secret_storage: 3, - }, - }) - ); - }); - function enumKeys< TEnum extends object, TKey extends keyof TEnum = keyof TEnum