diff --git a/src/connectionController.ts b/src/connectionController.ts index 9f62e0f20..a4aa8783a 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -806,6 +806,10 @@ export default class ConnectionController { this.eventEmitter.removeListener(eventType, listener); } + deactivate() { + this.eventEmitter.removeAllListeners(); + } + closeConnectionStringInput() { this._connectionStringInputCancellationToken?.cancel(); } diff --git a/src/editors/activeConnectionCodeLensProvider.ts b/src/editors/activeConnectionCodeLensProvider.ts index 62f227687..046b335eb 100644 --- a/src/editors/activeConnectionCodeLensProvider.ts +++ b/src/editors/activeConnectionCodeLensProvider.ts @@ -1,9 +1,10 @@ import * as vscode from 'vscode'; -import type { TextEditor } from 'vscode'; + import EXTENSION_COMMANDS from '../commands'; import type ConnectionController from '../connectionController'; import { isPlayground } from '../utils/playground'; import { getDBFromConnectionString } from '../utils/connection-string-db'; +import { DataServiceEventTypes } from '../connectionController'; export default class ActiveConnectionCodeLensProvider implements vscode.CodeLensProvider @@ -11,35 +12,29 @@ export default class ActiveConnectionCodeLensProvider _connectionController: ConnectionController; _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); - activeTextEditor?: TextEditor; + _activeConnectionChangedHandler: () => void; readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; constructor(connectionController: ConnectionController) { this._connectionController = connectionController; - this.activeTextEditor = vscode.window.activeTextEditor; vscode.workspace.onDidChangeConfiguration(() => { this._onDidChangeCodeLenses.fire(); }); - } - - setActiveTextEditor(activeTextEditor?: TextEditor) { - this.activeTextEditor = activeTextEditor; - this._onDidChangeCodeLenses.fire(); - } - - refresh(): void { - this._onDidChangeCodeLenses.fire(); - } - isPlayground(): boolean { - return isPlayground(this.activeTextEditor?.document.uri); + this._activeConnectionChangedHandler = () => { + this._onDidChangeCodeLenses.fire(); + }; + this._connectionController.addEventListener( + DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED, + this._activeConnectionChangedHandler + ); } - provideCodeLenses(): vscode.CodeLens[] { - if (!this.isPlayground()) { + provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] { + if (!isPlayground(document.uri)) { return []; } @@ -69,4 +64,11 @@ export default class ActiveConnectionCodeLensProvider return [codeLens]; } + + deactivate() { + this._connectionController.removeEventListener( + DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED, + this._activeConnectionChangedHandler + ); + } } diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 211682485..3e152ed0f 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -6,7 +6,6 @@ import vm from 'vm'; import os from 'os'; import transpiler from 'bson-transpilers'; -import type ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider'; import type PlaygroundSelectedCodeActionProvider from './playgroundSelectedCodeActionProvider'; import type ConnectionController from '../connectionController'; import { DataServiceEventTypes } from '../connectionController'; @@ -129,12 +128,12 @@ export default class PlaygroundController { _isPartialRun = false; - private _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; private _playgroundResultViewColumn?: vscode.ViewColumn; private _playgroundResultTextDocument?: vscode.TextDocument; private _statusView: StatusView; private _playgroundResultViewProvider: PlaygroundResultProvider; private _participantController: ParticipantController; + private _activeConnectionChangedHandler: () => void; private _codeToEvaluate = ''; @@ -144,7 +143,6 @@ export default class PlaygroundController { telemetryService, statusView, playgroundResultViewProvider, - activeConnectionCodeLensProvider, exportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider, participantController, @@ -154,7 +152,6 @@ export default class PlaygroundController { telemetryService: TelemetryService; statusView: StatusView; playgroundResultViewProvider: PlaygroundResultProvider; - activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider; participantController: ParticipantController; @@ -165,17 +162,17 @@ export default class PlaygroundController { this._telemetryService = telemetryService; this._statusView = statusView; this._playgroundResultViewProvider = playgroundResultViewProvider; - this._activeConnectionCodeLensProvider = activeConnectionCodeLensProvider; this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider; this._playgroundSelectedCodeActionProvider = playgroundSelectedCodeActionProvider; this._participantController = participantController; + this._activeConnectionChangedHandler = () => { + void this._activeConnectionChanged(); + }; this._connectionController.addEventListener( DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED, - () => { - void this._activeConnectionChanged(); - } + this._activeConnectionChangedHandler ); const onDidChangeActiveTextEditor = ( @@ -195,9 +192,6 @@ export default class PlaygroundController { if (isPlaygroundEditor) { this._activeTextEditor = editor; - this._activeConnectionCodeLensProvider.setActiveTextEditor( - this._activeTextEditor - ); this._playgroundSelectedCodeActionProvider.setActiveTextEditor( this._activeTextEditor ); @@ -276,8 +270,6 @@ export default class PlaygroundController { const connectionId = this._connectionController.getActiveConnectionId(); let mongoClientOption; - this._activeConnectionCodeLensProvider.refresh(); - if (dataService && connectionId) { mongoClientOption = this._connectionController.getMongoClientConnectionOptions(); @@ -919,9 +911,7 @@ export default class PlaygroundController { deactivate(): void { this._connectionController.removeEventListener( DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED, - () => { - // No action is required after removing the listener. - } + this._activeConnectionChangedHandler ); } } diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index c6fee71d1..789ed84e5 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -33,7 +33,9 @@ import launchMongoShell from './commands/launchMongoShell'; import type SchemaTreeItem from './explorer/schemaTreeItem'; import { StatusView } from './views'; import { StorageController, StorageVariables } from './storage'; -import TelemetryService from './telemetry/telemetryService'; +import TelemetryService, { + TelemetryEventTypes, +} from './telemetry/telemetryService'; import type PlaygroundsTreeItem from './explorer/playgroundsTreeItem'; import PlaygroundResultProvider from './editors/playgroundResultProvider'; import WebviewController from './views/webviewController'; @@ -117,7 +119,6 @@ export default class MDBExtensionController implements vscode.Disposable { telemetryService: this._telemetryService, statusView: this._statusView, playgroundResultViewProvider: this._playgroundResultViewProvider, - activeConnectionCodeLensProvider: this._activeConnectionCodeLensProvider, exportToLanguageCodeLensProvider: this._exportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: this._playgroundSelectedCodeActionProvider, @@ -157,6 +158,7 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommands(); this.showOverviewPageIfRecentlyInstalled(); + void this.showSurveyForEstablishedUsers(); } registerCommands = (): void => { @@ -887,6 +889,51 @@ export default class MDBExtensionController implements vscode.Disposable { } } + async showSurveyForEstablishedUsers(): Promise { + const surveyId = '9viN9wcbsC3zvHyg7'; + + const hasBeenShownSurveyAlready = + this._storageController.get(StorageVariables.GLOBAL_SURVEY_SHOWN) === + surveyId; + + // Show the survey when it hasn't been show to the + // user yet, and they have saved connections + // -> they haven't just started using this extension + if ( + hasBeenShownSurveyAlready || + !this._connectionStorage.hasSavedConnections() + ) { + return; + } + + const action = 'Share your thoughts'; + const text = 'How can we make the MongoDB extension better for you?'; + const link = 'https://forms.gle/9viN9wcbsC3zvHyg7'; + const result = await vscode.window.showInformationMessage( + text, + {}, + { + title: action, + } + ); + if (result?.title === action) { + void vscode.env.openExternal(vscode.Uri.parse(link)); + this._telemetryService.track(TelemetryEventTypes.SURVEY_CLICKED, { + survey_id: surveyId, + }); + } else { + this._telemetryService.track(TelemetryEventTypes.SURVEY_DISMISSED, { + survey_id: surveyId, + }); + } + + // whether action was taken or the prompt dismissed, we won't show this again + void this._storageController.update( + StorageVariables.GLOBAL_SURVEY_SHOWN, + surveyId + ); + } + async dispose(): Promise { await this.deactivate(); } @@ -902,5 +949,7 @@ export default class MDBExtensionController implements vscode.Disposable { this._telemetryService.deactivate(); this._editorsController.deactivate(); this._webviewController.deactivate(); + this._activeConnectionCodeLensProvider.deactivate(); + this._connectionController.deactivate(); } } diff --git a/src/storage/storageController.ts b/src/storage/storageController.ts index 7a2ba780b..e19eae149 100644 --- a/src/storage/storageController.ts +++ b/src/storage/storageController.ts @@ -6,6 +6,7 @@ import type { StoreConnectionInfo } from './connectionStorage'; export enum StorageVariables { // Only exists on globalState. GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW = 'GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW', + GLOBAL_SURVEY_SHOWN = 'GLOBAL_SURVEY_SHOWN', GLOBAL_SAVED_CONNECTIONS = 'GLOBAL_SAVED_CONNECTIONS', // Analytics user identify. GLOBAL_USER_ID = 'GLOBAL_USER_ID', @@ -51,6 +52,7 @@ interface StorageVariableContents { [StorageVariables.GLOBAL_USER_ID]: string; [StorageVariables.GLOBAL_ANONYMOUS_ID]: string; [StorageVariables.GLOBAL_HAS_BEEN_SHOWN_INITIAL_VIEW]: boolean; + [StorageVariables.GLOBAL_SURVEY_SHOWN]: string; [StorageVariables.GLOBAL_SAVED_CONNECTIONS]: ConnectionsFromStorage; [StorageVariables.WORKSPACE_SAVED_CONNECTIONS]: ConnectionsFromStorage; [StorageVariables.COPILOT_HAS_BEEN_SHOWN_WELCOME_MESSAGE]: boolean; diff --git a/src/telemetry/telemetryService.ts b/src/telemetry/telemetryService.ts index a38b55a89..f3bcb1bf4 100644 --- a/src/telemetry/telemetryService.ts +++ b/src/telemetry/telemetryService.ts @@ -77,6 +77,10 @@ type ConnectionEditedTelemetryEventProperties = { success: boolean; }; +type SurveyActionProperties = { + survey_id: string; +}; + type SavedConnectionsLoadedProperties = { // Total number of connections saved on disk saved_connections: number; @@ -102,7 +106,8 @@ export type TelemetryEventProperties = | PlaygroundSavedTelemetryEventProperties | PlaygroundLoadedTelemetryEventProperties | KeytarSecretsMigrationFailedProperties - | SavedConnectionsLoadedProperties; + | SavedConnectionsLoadedProperties + | SurveyActionProperties; export enum TelemetryEventTypes { PLAYGROUND_CODE_EXECUTED = 'Playground Code Executed', @@ -120,6 +125,8 @@ export enum TelemetryEventTypes { PLAYGROUND_CREATED = 'Playground Created', KEYTAR_SECRETS_MIGRATION_FAILED = 'Keytar Secrets Migration Failed', SAVED_CONNECTIONS_LOADED = 'Saved Connections Loaded', + SURVEY_CLICKED = 'Survey link clicked', + SURVEY_DISMISSED = 'Survey prompt dismissed', } /** diff --git a/src/test/suite/editors/activeConnectionCodeLensProvider.test.ts b/src/test/suite/editors/activeConnectionCodeLensProvider.test.ts index 827c928f7..0af23e134 100644 --- a/src/test/suite/editors/activeConnectionCodeLensProvider.test.ts +++ b/src/test/suite/editors/activeConnectionCodeLensProvider.test.ts @@ -1,8 +1,9 @@ import * as vscode from 'vscode'; import { beforeEach, afterEach } from 'mocha'; -import chai from 'chai'; +import { expect } from 'chai'; import sinon from 'sinon'; import type { DataService } from 'mongodb-data-service'; +import path from 'path'; import ActiveConnectionCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; import ConnectionController from '../../../connectionController'; @@ -12,8 +13,6 @@ import { ExtensionContextStub } from '../stubs'; import TelemetryService from '../../../telemetry/telemetryService'; import { TEST_DATABASE_URI } from '../dbTestHelper'; -const expect = chai.expect; - suite('Active Connection CodeLens Provider Test Suite', () => { const extensionContextStub = new ExtensionContextStub(); const testStorageController = new StorageController(extensionContextStub); @@ -42,19 +41,23 @@ suite('Active Connection CodeLens Provider Test Suite', () => { }); suite('the MongoDB playground in JS', () => { + const mockFileName = path.join('nonexistent', 'playground-test.mongodb.js'); + const mockDocumentUri = vscode.Uri.from({ + path: mockFileName, + scheme: 'untitled', + }); + const mockTextDoc: vscode.TextDocument = { + uri: mockDocumentUri, + } as Pick as vscode.TextDocument; + suite('user is not connected', () => { beforeEach(() => { - testCodeLensProvider.setActiveTextEditor( - vscode.window.activeTextEditor - ); const fakeShowQuickPick = sandbox.fake(); sandbox.replace(vscode.window, 'showQuickPick', fakeShowQuickPick); - const fakeIsPlayground = sandbox.fake.returns(true); - sandbox.replace(testCodeLensProvider, 'isPlayground', fakeIsPlayground); }); test('show disconnected message in code lenses', () => { - const codeLens = testCodeLensProvider.provideCodeLenses(); + const codeLens = testCodeLensProvider.provideCodeLenses(mockTextDoc); expect(codeLens).to.be.an('array'); expect(codeLens.length).to.be.equal(1); @@ -89,16 +92,11 @@ suite('Active Connection CodeLens Provider Test Suite', () => { } as unknown as DataService; testConnectionController.setActiveDataService(activeDataServiceStub); - testCodeLensProvider.setActiveTextEditor( - vscode.window.activeTextEditor - ); sandbox.replace( testConnectionController, 'getActiveConnectionName', sandbox.fake.returns('fakeName') ); - const fakeIsPlayground = sandbox.fake.returns(true); - sandbox.replace(testCodeLensProvider, 'isPlayground', fakeIsPlayground); }); test('show active connection in code lenses', () => { @@ -109,7 +107,7 @@ suite('Active Connection CodeLens Provider Test Suite', () => { url: TEST_DATABASE_URI, }) ); - const codeLens = testCodeLensProvider.provideCodeLenses(); + const codeLens = testCodeLensProvider.provideCodeLenses(mockTextDoc); expect(codeLens).to.be.an('array'); expect(codeLens.length).to.be.equal(1); @@ -131,7 +129,7 @@ suite('Active Connection CodeLens Provider Test Suite', () => { url: `${TEST_DATABASE_URI}/fakeDBName`, }) ); - const codeLens = testCodeLensProvider.provideCodeLenses(); + const codeLens = testCodeLensProvider.provideCodeLenses(mockTextDoc); expect(codeLens).to.be.an('array'); expect(codeLens.length).to.be.equal(1); expect(codeLens[0].command?.title).to.be.equal( @@ -147,16 +145,23 @@ suite('Active Connection CodeLens Provider Test Suite', () => { }); suite('the regular JS file', () => { + const mockFileName = path.join('nonexistent', 'playground-test.js'); + const mockDocumentUri = vscode.Uri.from({ + path: mockFileName, + scheme: 'untitled', + }); + const mockTextDoc: vscode.TextDocument = { + uri: mockDocumentUri, + } as Pick as vscode.TextDocument; + suite('user is not connected', () => { beforeEach(() => { const fakeShowQuickPick = sandbox.fake(); sandbox.replace(vscode.window, 'showQuickPick', fakeShowQuickPick); - const fakeIsPlayground = sandbox.fake.returns(false); - sandbox.replace(testCodeLensProvider, 'isPlayground', fakeIsPlayground); }); test('show not show the active connection code lenses', () => { - const codeLens = testCodeLensProvider.provideCodeLenses(); + const codeLens = testCodeLensProvider.provideCodeLenses(mockTextDoc); expect(codeLens).to.be.an('array'); expect(codeLens.length).to.be.equal(0); @@ -191,12 +196,10 @@ suite('Active Connection CodeLens Provider Test Suite', () => { 'getActiveConnectionName', sandbox.fake.returns('fakeName') ); - const fakeIsPlayground = sandbox.fake.returns(false); - sandbox.replace(testCodeLensProvider, 'isPlayground', fakeIsPlayground); }); - test('show not show the active connection code lensess', () => { - const codeLens = testCodeLensProvider.provideCodeLenses(); + test('show not show the active connection code lenses', () => { + const codeLens = testCodeLensProvider.provideCodeLenses(mockTextDoc); expect(codeLens).to.be.an('array'); expect(codeLens.length).to.be.equal(0); diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index c29343c69..fd3c7c910 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -8,7 +8,6 @@ import { v4 as uuidv4 } from 'uuid'; import path from 'path'; import chaiAsPromised from 'chai-as-promised'; -import ActiveDBCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; import PlaygroundSelectedCodeActionProvider from '../../../editors/playgroundSelectedCodeActionProvider'; import ConnectionController from '../../../connectionController'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; @@ -51,7 +50,6 @@ suite('Playground Controller Test Suite', function () { let testConnectionController: ConnectionController; let testEditDocumentCodeLensProvider: EditDocumentCodeLensProvider; let testPlaygroundResultProvider: PlaygroundResultProvider; - let testActiveDBCodeLensProvider: ActiveDBCodeLensProvider; let testExportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; let testCodeActionProvider: PlaygroundSelectedCodeActionProvider; let languageServerControllerStub: LanguageServerController; @@ -80,9 +78,6 @@ suite('Playground Controller Test Suite', function () { testConnectionController, testEditDocumentCodeLensProvider ); - testActiveDBCodeLensProvider = new ActiveDBCodeLensProvider( - testConnectionController - ); testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); @@ -100,7 +95,6 @@ suite('Playground Controller Test Suite', function () { telemetryService: testTelemetryService, statusView: testStatusView, playgroundResultViewProvider: testPlaygroundResultProvider, - activeConnectionCodeLensProvider: testActiveDBCodeLensProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, participantController: testParticipantController, @@ -356,7 +350,6 @@ suite('Playground Controller Test Suite', function () { telemetryService: testTelemetryService, statusView: testStatusView, playgroundResultViewProvider: testPlaygroundResultProvider, - activeConnectionCodeLensProvider: testActiveDBCodeLensProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, @@ -375,7 +368,6 @@ suite('Playground Controller Test Suite', function () { telemetryService: testTelemetryService, statusView: testStatusView, playgroundResultViewProvider: testPlaygroundResultProvider, - activeConnectionCodeLensProvider: testActiveDBCodeLensProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, diff --git a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts index d1ac86c1e..9b5a0d421 100644 --- a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts +++ b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts @@ -3,7 +3,6 @@ import { beforeEach, afterEach } from 'mocha'; import chai from 'chai'; import sinon from 'sinon'; -import ActiveConnectionCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; import PlaygroundSelectedCodeActionProvider from '../../../editors/playgroundSelectedCodeActionProvider'; import { LanguageServerController } from '../../../language'; @@ -77,10 +76,6 @@ suite('Playground Selected CodeAction Provider Test Suite', function () { TEST_DATABASE_URI ); - const activeConnectionCodeLensProvider = - new ActiveConnectionCodeLensProvider( - mdbTestExtension.testExtensionController._connectionController - ); const testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); @@ -96,7 +91,6 @@ suite('Playground Selected CodeAction Provider Test Suite', function () { playgroundResultViewProvider: mdbTestExtension.testExtensionController ._playgroundResultViewProvider, - activeConnectionCodeLensProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, diff --git a/src/test/suite/language/languageServerController.test.ts b/src/test/suite/language/languageServerController.test.ts index b86f2f975..5c0a7b4aa 100644 --- a/src/test/suite/language/languageServerController.test.ts +++ b/src/test/suite/language/languageServerController.test.ts @@ -8,7 +8,6 @@ import type { SinonStub } from 'sinon'; import type { DataService } from 'mongodb-data-service'; import chaiAsPromised from 'chai-as-promised'; -import ActiveDBCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; import PlaygroundSelectedCodeActionProvider from '../../../editors/playgroundSelectedCodeActionProvider'; import ConnectionController from '../../../connectionController'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; @@ -52,9 +51,6 @@ suite('Language Server Controller Test Suite', () => { testConnectionController, testEditDocumentCodeLensProvider ); - const testActiveDBCodeLensProvider = new ActiveDBCodeLensProvider( - testConnectionController - ); const testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); const testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); @@ -79,7 +75,6 @@ suite('Language Server Controller Test Suite', () => { telemetryService: testTelemetryService, statusView: testStatusView, playgroundResultViewProvider: testPlaygroundResultProvider, - activeConnectionCodeLensProvider: testActiveDBCodeLensProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, playgroundSelectedCodeActionProvider: testCodeActionProvider, participantController: testParticipantController, diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 9fc872a19..e887485b9 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -175,6 +175,7 @@ suite('MDBExtensionController Test Suite', function () { let fakeActiveConnectionId: SinonSpy; let showErrorMessageStub: SinonStub; let fakeCreatePlaygroundFileWithContent: SinonSpy; + let openExternalStub: SinonStub; const sandbox = sinon.createSandbox(); @@ -183,6 +184,7 @@ suite('MDBExtensionController Test Suite', function () { vscode.window, 'showInformationMessage' ); + openExternalStub = sandbox.stub(vscode.env, 'openExternal'); openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument'); fakeActiveConnectionId = sandbox.fake.returns('tasty_sandwhich'); sandbox.replace( @@ -1717,5 +1719,138 @@ suite('MDBExtensionController Test Suite', function () { }); }); }); + + suite('survey prompt', function () { + suite( + "when a user hasn't been shown the survey prompt yet, and they have connections saved", + () => { + [ + { + description: 'clicked the button', + value: { title: 'Share your thoughts' }, + }, + { description: 'dismissed', value: undefined }, + ].forEach((reaction) => { + suite(`user ${reaction.description}`, () => { + let connectionsUpdateStub: SinonStub; + let uriParseStub: SinonStub; + beforeEach(async () => { + showInformationMessageStub.resolves(reaction.value); + openExternalStub.resolves(undefined); + sandbox.replace( + mdbTestExtension.testExtensionController._storageController, + 'get', + sandbox.fake.returns(undefined) + ); + sandbox.replace( + mdbTestExtension.testExtensionController._connectionStorage, + 'hasSavedConnections', + sandbox.fake.returns(true) + ); + connectionsUpdateStub = sandbox.stub( + mdbTestExtension.testExtensionController._storageController, + 'update' + ); + uriParseStub = sandbox.stub(vscode.Uri, 'parse'); + connectionsUpdateStub.resolves(undefined); + await mdbTestExtension.testExtensionController.showSurveyForEstablishedUsers(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + test('they are shown the survey prompt', () => { + assert(showInformationMessageStub.called); + assert.strictEqual( + showInformationMessageStub.firstCall.args[0], + 'How can we make the MongoDB extension better for you?' + ); + }); + + test('the link was open if and only if they click the button', () => { + if (reaction.value === undefined) { + assert(openExternalStub.notCalled); + } + if (reaction.value) { + assert(openExternalStub.called); + assert(uriParseStub.called); + assert.strictEqual( + uriParseStub.firstCall.args[0], + 'https://forms.gle/9viN9wcbsC3zvHyg7' + ); + } + }); + + test("it sets that they've been shown the survey", () => { + assert(connectionsUpdateStub.called); + assert.strictEqual( + connectionsUpdateStub.firstCall.args[0], + StorageVariables.GLOBAL_SURVEY_SHOWN + ); + assert.strictEqual( + connectionsUpdateStub.firstCall.args[1], + '9viN9wcbsC3zvHyg7' + ); + }); + }); + }); + } + ); + + suite('when a user has been shown the survey prompt already', () => { + let connectionsUpdateStub: SinonStub; + beforeEach(() => { + sandbox.replace( + mdbTestExtension.testExtensionController._storageController, + 'get', + sandbox.fake.returns('9viN9wcbsC3zvHyg7') // survey has been shown + ); + sandbox.replace( + mdbTestExtension.testExtensionController._connectionStorage, + 'hasSavedConnections', + sandbox.fake.returns(true) + ); + connectionsUpdateStub = sandbox.stub( + mdbTestExtension.testExtensionController._storageController, + 'update' + ); + connectionsUpdateStub.resolves(undefined); + + void mdbTestExtension.testExtensionController.showSurveyForEstablishedUsers(); + }); + + test('they are not shown the survey prompt', () => { + assert(showInformationMessageStub.notCalled); + }); + }); + + suite('when a has no connections saved', () => { + let connectionsUpdateStub: SinonStub; + beforeEach(() => { + sandbox.replace( + mdbTestExtension.testExtensionController._storageController, + 'get', + sandbox.fake.returns(undefined) + ); + sandbox.replace( + mdbTestExtension.testExtensionController._connectionStorage, + 'hasSavedConnections', + sandbox.fake.returns(false) // no connections yet - this might be the first install + ); + connectionsUpdateStub = sandbox.stub( + mdbTestExtension.testExtensionController._storageController, + 'update' + ); + connectionsUpdateStub.resolves(undefined); + + void mdbTestExtension.testExtensionController.showSurveyForEstablishedUsers(); + }); + + test('they are not shown the survey prompt', () => { + assert(showInformationMessageStub.notCalled); + }); + }); + }); }); }); diff --git a/src/test/suite/views/webview-app/overview-page.test.tsx b/src/test/suite/views/webview-app/overview-page.test.tsx index 3dc9abbbf..459e2d75b 100644 --- a/src/test/suite/views/webview-app/overview-page.test.tsx +++ b/src/test/suite/views/webview-app/overview-page.test.tsx @@ -39,7 +39,9 @@ describe('OverviewPage test suite', function () { describe('Connection Form', function () { // Rendering the connection form takes ~4 seconds, so we need to increase the timeout. // Not sure on the cause of this slowdown, it could be animation based. - this.timeout(20000); + // Without this it's flaky on mac CI. + // TODO(COMPASS-7762): Once we update the connection form this may be able to go away. + this.timeout(25000); it('is able to open and close the new connection form', async function () { render();