diff --git a/src/connectionController.ts b/src/connectionController.ts index a08baac59..ca65bdb68 100644 --- a/src/connectionController.ts +++ b/src/connectionController.ts @@ -90,6 +90,8 @@ export default class ConnectionController { private _currentConnectionId: null | string = null; _connectionAttempt: null | ConnectionAttempt = null; + _connectionStringInputCancellationToken: null | vscode.CancellationTokenSource = + null; private _connectingConnectionId: null | string = null; private _disconnecting = false; @@ -144,33 +146,44 @@ export default class ConnectionController { log.info('connectWithURI command called'); - try { - connectionString = await vscode.window.showInputBox({ - value: '', - ignoreFocusOut: true, - placeHolder: - 'e.g. mongodb+srv://username:password@cluster0.mongodb.net/admin', - prompt: 'Enter your connection string (SRV or standard)', - validateInput: (uri: string) => { - if ( - !uri.startsWith('mongodb://') && - !uri.startsWith('mongodb+srv://') - ) { - return 'MongoDB connection strings begin with "mongodb://" or "mongodb+srv://"'; - } + const cancellationToken = new vscode.CancellationTokenSource(); + this._connectionStringInputCancellationToken = cancellationToken; - try { - // eslint-disable-next-line no-new - new ConnectionString(uri); - } catch (error) { - return formatError(error).message; - } - - return null; + try { + connectionString = await vscode.window.showInputBox( + { + value: '', + ignoreFocusOut: true, + placeHolder: + 'e.g. mongodb+srv://username:password@cluster0.mongodb.net/admin', + prompt: 'Enter your connection string (SRV or standard)', + validateInput: (uri: string) => { + if ( + !uri.startsWith('mongodb://') && + !uri.startsWith('mongodb+srv://') + ) { + return 'MongoDB connection strings begin with "mongodb://" or "mongodb+srv://"'; + } + + try { + // eslint-disable-next-line no-new + new ConnectionString(uri); + } catch (error) { + return formatError(error).message; + } + + return null; + }, }, - }); + cancellationToken.token + ); } catch (e) { return false; + } finally { + if (this._connectionStringInputCancellationToken === cancellationToken) { + this._connectionStringInputCancellationToken.dispose(); + this._connectionStringInputCancellationToken = null; + } } if (!connectionString) { @@ -687,6 +700,10 @@ export default class ConnectionController { this.eventEmitter.removeListener(eventType, listener); } + closeConnectionStringInput() { + this._connectionStringInputCancellationToken?.cancel(); + } + isConnecting(): boolean { return !!this._connectionAttempt; } diff --git a/src/test/suite/connectionController.test.ts b/src/test/suite/connectionController.test.ts index a2f419755..fef2704f0 100644 --- a/src/test/suite/connectionController.test.ts +++ b/src/test/suite/connectionController.test.ts @@ -593,6 +593,27 @@ suite('Connection Controller Test Suite', function () { assert.strictEqual(name, 'new connection name'); }); + test('close connection string input calls to cancel the cancellation token', function (done) { + const inputBoxResolvesStub = sandbox.stub(); + inputBoxResolvesStub.callsFake(() => { + try { + const cancellationToken = inputBoxResolvesStub.firstCall.args[1]; + assert.strictEqual(cancellationToken.isCancellationRequested, false); + + testConnectionController.closeConnectionStringInput(); + + assert.strictEqual(cancellationToken.isCancellationRequested, true); + } catch (err) { + done(err); + } + + done(); + }); + sandbox.replace(vscode.window, 'showInputBox', inputBoxResolvesStub); + + void testConnectionController.connectWithURI(); + }); + test('ConnectionQuickPicks workspace connections list is displayed in the alphanumerical case insensitive order', async () => { await vscode.workspace .getConfiguration('mdb.connectionSaving') 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 afd371e09..84addf5e7 100644 --- a/src/test/suite/views/webview-app/overview-page.test.tsx +++ b/src/test/suite/views/webview-app/overview-page.test.tsx @@ -41,9 +41,15 @@ describe('OverviewPage test suite', function () { render(); expect(screen.queryByTestId(connectionFormTestId)).to.not.exist; + const postMessageSpy = Sinon.spy(vscode, 'postMessage'); + expect(postMessageSpy).to.not.be.called; screen.getByText('Open form').click(); expect(screen.getByTestId(connectionFormTestId)).to.exist; + const message = postMessageSpy.firstCall.args[0]; + expect(message).to.deep.equal({ + command: MESSAGE_TYPES.CONNECTION_FORM_OPENED, + }); screen.getByLabelText('Close modal').click(); expect(screen.queryByTestId(connectionFormTestId)).to.not.exist; diff --git a/src/views/webview-app/extension-app-message-constants.ts b/src/views/webview-app/extension-app-message-constants.ts index aa3d71d1d..d17c40e3c 100644 --- a/src/views/webview-app/extension-app-message-constants.ts +++ b/src/views/webview-app/extension-app-message-constants.ts @@ -15,6 +15,7 @@ export enum MESSAGE_TYPES { CONNECT = 'CONNECT', CANCEL_CONNECT = 'CANCEL_CONNECT', CONNECT_RESULT = 'CONNECT_RESULT', + CONNECTION_FORM_OPENED = 'CONNECTION_FORM_OPENED', CONNECTION_STATUS_MESSAGE = 'CONNECTION_STATUS_MESSAGE', EXTENSION_LINK_CLICKED = 'EXTENSION_LINK_CLICKED', CREATE_NEW_PLAYGROUND = 'CREATE_NEW_PLAYGROUND', @@ -33,6 +34,10 @@ export interface CreateNewPlaygroundMessage extends BasicWebviewMessage { command: MESSAGE_TYPES.CREATE_NEW_PLAYGROUND; } +export interface ConnectionFormOpenedMessage extends BasicWebviewMessage { + command: MESSAGE_TYPES.CONNECTION_FORM_OPENED; +} + export interface ConnectionStatusMessage extends BasicWebviewMessage { command: MESSAGE_TYPES.CONNECTION_STATUS_MESSAGE; connectionStatus: CONNECTION_STATUS; @@ -90,6 +95,7 @@ export interface ThemeChangedMessage extends BasicWebviewMessage { export type MESSAGE_FROM_WEBVIEW_TO_EXTENSION = | ConnectMessage | CancelConnectMessage + | ConnectionFormOpenedMessage | CreateNewPlaygroundMessage | GetConnectionStatusMessage | LinkClickedMessage diff --git a/src/views/webview-app/use-connection-form.ts b/src/views/webview-app/use-connection-form.ts index 1e9f44427..1f7c39613 100644 --- a/src/views/webview-app/use-connection-form.ts +++ b/src/views/webview-app/use-connection-form.ts @@ -4,6 +4,7 @@ import type { ConnectionOptions } from 'mongodb-data-service'; import { sendConnectToExtension, sendCancelConnectToExtension, + sendFormOpenedToExtension, } from './vscode-api'; import { MESSAGE_TYPES } from './extension-app-message-constants'; import type { MESSAGE_FROM_EXTENSION_TO_WEBVIEW } from './extension-app-message-constants'; @@ -37,7 +38,10 @@ export default function useConnectionForm() { connectionFormOpened, isConnecting, connectionErrorMessage, - openConnectionForm: () => setConnectionFormOpened(true), + openConnectionForm: () => { + setConnectionFormOpened(true); + sendFormOpenedToExtension(); + }, closeConnectionForm: () => { setConnectionFormOpened(false); setConnectionErrorMessage(''); diff --git a/src/views/webview-app/vscode-api.ts b/src/views/webview-app/vscode-api.ts index 9759caf45..c52a3d66d 100644 --- a/src/views/webview-app/vscode-api.ts +++ b/src/views/webview-app/vscode-api.ts @@ -28,6 +28,14 @@ export const sendCancelConnectToExtension = () => { }); }; +// When the form is opened we want to close the connection string +// input if it's open, so we message the extension. +export const sendFormOpenedToExtension = () => { + vscode.postMessage({ + command: MESSAGE_TYPES.CONNECTION_FORM_OPENED, + }); +}; + export const renameActiveConnection = () => { vscode.postMessage({ command: MESSAGE_TYPES.RENAME_ACTIVE_CONNECTION, diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index c7ed454c6..7c4966aaf 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -159,6 +159,11 @@ export default class WebviewController { EXTENSION_COMMANDS.MDB_CREATE_PLAYGROUND_FROM_OVERVIEW_PAGE ); return; + case MESSAGE_TYPES.CONNECTION_FORM_OPENED: + // If the connection string input is open we want to close it + // when the user opens the form. + this._connectionController.closeConnectionStringInput(); + return; case MESSAGE_TYPES.GET_CONNECTION_STATUS: void panel.webview.postMessage({ command: MESSAGE_TYPES.CONNECTION_STATUS_MESSAGE,