From 4ba64e4bad99a0075841df99e6071f0ee38209f6 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Mon, 20 Apr 2020 18:46:19 +0200 Subject: [PATCH] Add connect with connection string to connect form --- .../suite/views/webviewController.test.ts | 52 ++++++- .../connect-form/connection-form.tsx | 20 ++- .../extension-app-message-constants.ts | 5 + src/views/webview-app/externals.ts | 9 +- src/views/webview-app/store/actions.ts | 6 + src/views/webview-app/store/store.ts | 143 ++++++++++-------- src/views/webviewController.ts | 14 +- 7 files changed, 176 insertions(+), 73 deletions(-) diff --git a/src/test/suite/views/webviewController.test.ts b/src/test/suite/views/webviewController.test.ts index 6aaee8e79..7feee0ba7 100644 --- a/src/test/suite/views/webviewController.test.ts +++ b/src/test/suite/views/webviewController.test.ts @@ -104,7 +104,7 @@ suite('Connect Form View Test Suite', () => { assert(testConnectionController.isCurrentlyConnected()); assert( testConnectionController.getActiveConnectionName() === - 'localhost:27018' + 'localhost:27018' ); assert( testConnectionController.getActiveConnectionModel()?.port === 27018 @@ -221,7 +221,6 @@ suite('Connect Form View Test Suite', () => { }); }); - test('web view sends an unsuccessful connect result on an unsuccessful connection', (done) => { const testExtensionContext = new TestExtensionContext(); const testStorageController = new StorageController(testExtensionContext); @@ -380,4 +379,53 @@ suite('Connect Form View Test Suite', () => { action: 'file_action' }); }); + + test('web view runs the "connectWithURI" command when open connection string input is recieved', (done) => { + const testExtensionContext = new TestExtensionContext(); + const testStorageController = new StorageController(testExtensionContext); + + const testConnectionController = new ConnectionController( + new StatusView(testExtensionContext), + testStorageController + ); + + let messageRecieved; + const fakeWebview = { + html: '', + onDidReceiveMessage: (callback): void => { + messageRecieved = callback; + } + }; + + const fakeVSCodeExecuteCommand = sinon.fake(); + sinon.replace(vscode.commands, 'executeCommand', fakeVSCodeExecuteCommand); + + const fakeVSCodeCreateWebviewPanel = sinon.fake.returns({ + webview: fakeWebview + }); + sinon.replace( + vscode.window, + 'createWebviewPanel', + fakeVSCodeCreateWebviewPanel + ); + + const testWebviewController = new WebviewController( + testConnectionController + ); + + testWebviewController.showConnectForm( + mdbTestExtension.testExtensionContext + ); + + messageRecieved({ + command: MESSAGE_TYPES.OPEN_CONNECTION_STRING_INPUT + }); + + setTimeout(() => { + assert(fakeVSCodeExecuteCommand.called); + assert(fakeVSCodeExecuteCommand.firstArg === 'mdb.connectWithURI'); + + done(); + }, 50); + }); }); diff --git a/src/views/webview-app/components/connect-form/connection-form.tsx b/src/views/webview-app/components/connect-form/connection-form.tsx index 872e3791e..50be768f0 100644 --- a/src/views/webview-app/components/connect-form/connection-form.tsx +++ b/src/views/webview-app/components/connect-form/connection-form.tsx @@ -2,7 +2,11 @@ import classnames from 'classnames'; import * as React from 'react'; import { connect } from 'react-redux'; -import { ActionTypes, ConnectionFormChangedAction } from '../../store/actions'; +import { + ActionTypes, + ConnectionFormChangedAction, + OpenConnectionStringInputAction +} from '../../store/actions'; import FormGroup from './form-group'; import HostInput from './host/host-input'; import PortInput from './host/port-input'; @@ -31,6 +35,7 @@ type stateProps = { type dispatchProps = { onConnectionFormChanged: () => void; + onOpenConnectionStringInput: () => void; }; type props = stateProps & dispatchProps; @@ -38,6 +43,10 @@ type props = stateProps & dispatchProps; class ConnectionForm extends React.Component { static displayName = 'ConnectionForm'; + onConnectWithConnectionStringClicked = (): void => { + this.props.onOpenConnectionStringInput(); + }; + /** * Renders a port input. * @@ -136,6 +145,12 @@ class ConnectionForm extends React.Component { className={classnames(styles['connect-form'])} >

Connect to MongoDB

+
+ Enter your connection details below, or{' '} + + connect with a connection string + +
{this.renderHostnameArea()} {this.renderConnectionOptionsArea()} @@ -170,6 +185,9 @@ const mapDispatchToProps: dispatchProps = { // Resets URL validation if form was changed. onConnectionFormChanged: (): ConnectionFormChangedAction => ({ type: ActionTypes.CONNECTION_FORM_CHANGED + }), + onOpenConnectionStringInput: (): OpenConnectionStringInputAction => ({ + type: ActionTypes.OPEN_CONNECTION_STRING_INPUT }) }; diff --git a/src/views/webview-app/extension-app-message-constants.ts b/src/views/webview-app/extension-app-message-constants.ts index 149eaf707..23beb2ed2 100644 --- a/src/views/webview-app/extension-app-message-constants.ts +++ b/src/views/webview-app/extension-app-message-constants.ts @@ -3,6 +3,7 @@ import { FilePickerActionTypes } from './store/actions'; export enum MESSAGE_TYPES { CONNECT = 'CONNECT', CONNECT_RESULT = 'CONNECT_RESULT', + OPEN_CONNECTION_STRING_INPUT = 'OPEN_CONNECTION_STRING_INPUT', OPEN_FILE_PICKER = 'OPEN_FILE_PICKER', FILE_PICKER_RESULTS = 'FILE_PICKER_RESULTS' } @@ -23,6 +24,10 @@ export interface ConnectResultsMessage extends BasicWebviewMessage { connectionMessage: string; } +export interface OpenConnectionStringInputMessage extends BasicWebviewMessage { + command: MESSAGE_TYPES.OPEN_CONNECTION_STRING_INPUT; +} + // Note: In the app this is tightly coupled with 'externals.ts'. export interface OpenFilePickerMessage extends BasicWebviewMessage { command: MESSAGE_TYPES.OPEN_FILE_PICKER; diff --git a/src/views/webview-app/externals.ts b/src/views/webview-app/externals.ts index 27e3d5c8a..efcdcaaad 100644 --- a/src/views/webview-app/externals.ts +++ b/src/views/webview-app/externals.ts @@ -13,13 +13,20 @@ interface ConnectMessage extends BasicWebviewMessage { connectionModel: object; } +interface OpenConnectionStringInputMessage extends BasicWebviewMessage { + command: 'OPEN_CONNECTION_STRING_INPUT'; +} + interface FilePickerMessage { command: 'OPEN_FILE_PICKER'; action: string; multi: boolean; } -type WebviewMessage = ConnectMessage | FilePickerMessage; +type WebviewMessage = + | ConnectMessage + | FilePickerMessage + | OpenConnectionStringInputMessage; interface VSCodeApi { postMessage: (message: WebviewMessage) => void; diff --git a/src/views/webview-app/store/actions.ts b/src/views/webview-app/store/actions.ts index f59e64476..7e9e9a1af 100644 --- a/src/views/webview-app/store/actions.ts +++ b/src/views/webview-app/store/actions.ts @@ -18,6 +18,7 @@ export enum ActionTypes { ON_CHANGE_SSL_CA = 'ON_CHANGE_SSL_CA', ON_CHANGE_SSL_CERT = 'ON_CHANGE_SSL_CERT', ON_CHANGE_SSL_KEY = 'ON_CHANGE_SSL_KEY', + OPEN_CONNECTION_STRING_INPUT = 'OPEN_CONNECTION_STRING_INPUT', PASSWORD_CHANGED = 'PASSWORD_CHANGED', PORT_CHANGED = 'PORT_CHANGED', READ_PREFERENCE_CHANGED = 'READ_PREFERENCE_CHANGED', @@ -125,6 +126,10 @@ export interface OnChangeSSLKeyAction extends BaseAction { type: ActionTypes.ON_CHANGE_SSL_KEY; } +export interface OpenConnectionStringInputAction extends BaseAction { + type: ActionTypes.OPEN_CONNECTION_STRING_INPUT; +} + export interface PasswordChangedAction extends BaseAction { type: ActionTypes.PASSWORD_CHANGED; mongodbPassword: string; @@ -236,6 +241,7 @@ export type Actions = | OnChangeSSLCAAction | OnChangeSSLCertAction | OnChangeSSLKeyAction + | OpenConnectionStringInputAction | PasswordChangedAction | PortChangedAction | ReadPreferenceChangedAction diff --git a/src/views/webview-app/store/store.ts b/src/views/webview-app/store/store.ts index 8ab07fb1e..e4b4cf584 100644 --- a/src/views/webview-app/store/store.ts +++ b/src/views/webview-app/store/store.ts @@ -1,7 +1,7 @@ import { Actions, ActionTypes, FilePickerActionTypes } from './actions'; import ConnectionModel, { - validateConnectionModel, + validateConnectionModel } from '../connection-model/connection-model'; import SSL_METHODS from '../connection-model/constants/ssl-methods'; import { MESSAGE_TYPES } from '../extension-app-message-constants'; @@ -29,7 +29,7 @@ export const initialState = { syntaxErrorMessage: '', isHostChanged: false, isPortChanged: false, - savedMessage: '', + savedMessage: '' }; const showFilePicker = ( @@ -39,7 +39,7 @@ const showFilePicker = ( vscode.postMessage({ command: MESSAGE_TYPES.OPEN_FILE_PICKER, action, - multi, + multi }); }; @@ -54,8 +54,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - mongodbDatabaseName: action.mongodbDatabaseName, - }, + mongodbDatabaseName: action.mongodbDatabaseName + } }; case ActionTypes.AUTH_STRATEGY_CHANGED: @@ -74,8 +74,8 @@ export const rootReducer = ( kerberosServiceName: undefined, x509Username: undefined, ldapUsername: undefined, - ldapPassword: undefined, - }, + ldapPassword: undefined + } }; case ActionTypes.CONNECT: @@ -83,7 +83,7 @@ export const rootReducer = ( return { ...state, isValid: false, - errorMessage: 'The required fields can not be empty.', + errorMessage: 'The required fields can not be empty.' }; } @@ -91,13 +91,13 @@ export const rootReducer = ( return { ...state, errorMessage: 'Already connecting, please wait.', - isValid: false, + isValid: false }; } vscode.postMessage({ command: MESSAGE_TYPES.CONNECT, - connectionModel: state.currentConnection, + connectionModel: state.currentConnection }); return { @@ -105,7 +105,7 @@ export const rootReducer = ( // The form may be displaying a previous error message from a failed connect. isValid: true, isConnecting: true, - isConnected: false, + isConnected: false }; case ActionTypes.CONNECTION_FORM_CHANGED: @@ -114,7 +114,7 @@ export const rootReducer = ( isValid: true, isConnected: false, errorMessage: '', - syntaxErrorMessage: '', + syntaxErrorMessage: '' }; case ActionTypes.CONNECTION_EVENT_OCCURED: @@ -125,7 +125,7 @@ export const rootReducer = ( isValid: action.successfullyConnected ? state.isValid : false, errorMessage: action.successfullyConnected ? state.errorMessage - : action.connectionMessage, + : action.connectionMessage }; case ActionTypes.HOSTNAME_CHANGED: @@ -137,8 +137,8 @@ export const rootReducer = ( hostname: action.hostname.trim(), sslMethod: /mongodb\.net/i.exec(action.hostname) ? SSL_METHODS.SYSTEMCA - : state.currentConnection.sslMethod, - }, + : state.currentConnection.sslMethod + } }; case ActionTypes.IS_SRV_RECORD_TOGGLED: @@ -146,8 +146,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - isSrvRecord: !state.currentConnection.isSrvRecord, - }, + isSrvRecord: !state.currentConnection.isSrvRecord + } }; case ActionTypes.KERBEROS_PARAMETERS_CHANGED: @@ -158,8 +158,8 @@ export const rootReducer = ( kerberosCanonicalizeHostname: action.kerberosCanonicalizeHostname, kerberosPassword: action.kerberosPassword, kerberosPrincipal: action.kerberosPrincipal, - kerberosServiceName: action.kerberosServiceName, - }, + kerberosServiceName: action.kerberosServiceName + } }; case ActionTypes.LDAP_PASSWORD_CHANGED: @@ -167,8 +167,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - ldapPassword: action.ldapPassword, - }, + ldapPassword: action.ldapPassword + } }; case ActionTypes.LDAP_USERNAME_CHANGED: @@ -176,8 +176,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - ldapUsername: action.ldapUsername, - }, + ldapUsername: action.ldapUsername + } }; case ActionTypes.ON_CHANGE_SSH_TUNNEL_IDENTITY_FILE: @@ -187,8 +187,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sshTunnelIdentityFile: undefined, - }, + sshTunnelIdentityFile: undefined + } }; case ActionTypes.ON_CHANGE_SSL_CA: @@ -198,8 +198,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sslCA: undefined, - }, + sslCA: undefined + } }; case ActionTypes.ON_CHANGE_SSL_CERT: @@ -209,8 +209,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sslCert: undefined, - }, + sslCert: undefined + } }; case ActionTypes.ON_CHANGE_SSL_KEY: @@ -220,8 +220,17 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sslKey: undefined, - }, + sslKey: undefined + } + }; + + case ActionTypes.OPEN_CONNECTION_STRING_INPUT: + vscode.postMessage({ + command: MESSAGE_TYPES.OPEN_CONNECTION_STRING_INPUT + }); + + return { + ...state }; case ActionTypes.PASSWORD_CHANGED: @@ -229,8 +238,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - mongodbPassword: action.mongodbPassword, - }, + mongodbPassword: action.mongodbPassword + } }; case ActionTypes.PORT_CHANGED: @@ -239,8 +248,8 @@ export const rootReducer = ( isPortChanged: true, currentConnection: { ...state.currentConnection, - port: action.port, - }, + port: action.port + } }; case ActionTypes.READ_PREFERENCE_CHANGED: @@ -248,8 +257,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - readPreference: action.readPreference, - }, + readPreference: action.readPreference + } }; case ActionTypes.REPLICA_SET_CHANGED: @@ -257,8 +266,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - replicaSet: action.replicaSet, - }, + replicaSet: action.replicaSet + } }; case ActionTypes.SSH_TUNNEL_CHANGED: @@ -274,8 +283,8 @@ export const rootReducer = ( sshTunnelPassword: undefined, sshTunnelIdentityFile: undefined, sshTunnelPassphrase: undefined, - replicaSet: undefined, - }, + replicaSet: undefined + } }; case ActionTypes.SSH_TUNNEL_HOSTNAME_CHANGED: @@ -283,8 +292,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sshTunnelHostname: action.sshTunnelHostname, - }, + sshTunnelHostname: action.sshTunnelHostname + } }; case ActionTypes.SSH_TUNNEL_IDENTITY_FILE_CHANGED: @@ -292,8 +301,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sshTunnelIdentityFile: action.files, - }, + sshTunnelIdentityFile: action.files + } }; case ActionTypes.SSH_TUNNEL_PASSPHRASE_CHANGED: @@ -301,8 +310,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sshTunnelPassphrase: action.sshTunnelPassphrase, - }, + sshTunnelPassphrase: action.sshTunnelPassphrase + } }; case ActionTypes.SSH_TUNNEL_PASSWORD_CHANGED: @@ -310,8 +319,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sshTunnelPassword: action.sshTunnelPassword, - }, + sshTunnelPassword: action.sshTunnelPassword + } }; case ActionTypes.SSH_TUNNEL_PORT_CHANGED: @@ -319,8 +328,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sshTunnelPort: action.sshTunnelPort, - }, + sshTunnelPort: action.sshTunnelPort + } }; case ActionTypes.SSH_TUNNEL_USERNAME_CHANGED: @@ -328,8 +337,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sshTunnelUsername: action.sshTunnelUsername, - }, + sshTunnelUsername: action.sshTunnelUsername + } }; case ActionTypes.SSL_CA_CHANGED: @@ -337,8 +346,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sslCA: action.files, - }, + sslCA: action.files + } }; case ActionTypes.SSL_CERT_CHANGED: @@ -346,8 +355,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sslCert: action.files, - }, + sslCert: action.files + } }; case ActionTypes.SSL_KEY_CHANGED: @@ -355,8 +364,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sslKey: action.files, - }, + sslKey: action.files + } }; case ActionTypes.SSL_METHOD_CHANGED: @@ -369,8 +378,8 @@ export const rootReducer = ( sslCA: undefined, sslCert: undefined, sslKey: undefined, - sslPass: undefined, - }, + sslPass: undefined + } }; case ActionTypes.SSL_PASS_CHANGED: @@ -378,8 +387,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - sslPass: action.sslPass, - }, + sslPass: action.sslPass + } }; case ActionTypes.USERNAME_CHANGED: @@ -387,8 +396,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - mongodbUsername: action.mongodbUsername, - }, + mongodbUsername: action.mongodbUsername + } }; case ActionTypes.X509_USERNAME_CHANGED: @@ -396,8 +405,8 @@ export const rootReducer = ( ...state, currentConnection: { ...state.currentConnection, - x509Username: action.x509Username, - }, + x509Username: action.x509Username + } }; default: diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index 9f1d77e2b..93c48b358 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -5,7 +5,8 @@ import ConnectionController from '../connectionController'; import { MESSAGE_TYPES, ConnectMessage, - OpenFilePickerMessage + OpenFilePickerMessage, + OpenConnectionStringInputMessage } from './webview-app/extension-app-message-constants'; import { createLogger } from '../logging'; @@ -51,7 +52,10 @@ export default class WebviewController { } handleWebviewMessage = ( - message: ConnectMessage | OpenFilePickerMessage, + message: + | ConnectMessage + | OpenFilePickerMessage + | OpenConnectionStringInputMessage, panel: vscode.WebviewPanel ): void => { switch (message.command) { @@ -94,6 +98,12 @@ export default class WebviewController { }); }); return; + case MESSAGE_TYPES.OPEN_CONNECTION_STRING_INPUT: + vscode.commands.executeCommand( + 'mdb.connectWithURI' + ); + + return; default: // no-op. return;