support for connect with connection string (#19355)

* wip

* WIP

* radio button

* handle connection string

* fix test

* fix test

* fix test cases

* remember password

* pr comments

* pr comments
This commit is contained in:
Alan Ren
2022-05-13 14:48:12 -07:00
committed by GitHub
parent 128767c713
commit b8858f011d
12 changed files with 301 additions and 161 deletions

View File

@@ -767,6 +767,9 @@
"supportedExecutionPlanFileExtensions": [ "supportedExecutionPlanFileExtensions": [
"sqlplan" "sqlplan"
], ],
"connectionStringOptions": {
"isEnabled": true
},
"iconPath": [ "iconPath": [
{ {
"id": "mssql:cloud", "id": "mssql:cloud",

View File

@@ -40,6 +40,7 @@ export class RadioButton extends Widget {
this.label = opts.label; this.label = opts.label;
this.enabled = opts.enabled || true; this.enabled = opts.enabled || true;
this.checked = opts.checked || false; this.checked = opts.checked || false;
this._internalCheckedStateTracker = this.checked;
this.onclick(this.inputElement, () => { this.onclick(this.inputElement, () => {
this._onClicked.fire(); this._onClicked.fire();
this.checked = true; this.checked = true;

View File

@@ -20,6 +20,20 @@ export const clientCapabilities = {
hostVersion: HOST_VERSION hostVersion: HOST_VERSION
}; };
/**
* The connection string options for connection provider.
*/
export interface ConnectionStringOptions {
/**
* Whether the connection provider supports connection string as an input option. The default value is false.
*/
isEnabled?: boolean;
/**
* Whether the connection provider uses connection string as the default option to connect. The default value is false.
*/
isDefault?: boolean;
}
export interface ConnectionProviderProperties { export interface ConnectionProviderProperties {
providerId: string; providerId: string;
iconPath?: URI | IconPath | { id: string, path: IconPath, default?: boolean }[] iconPath?: URI | IconPath | { id: string, path: IconPath, default?: boolean }[]
@@ -29,6 +43,7 @@ export interface ConnectionProviderProperties {
connectionOptions: azdata.ConnectionOption[]; connectionOptions: azdata.ConnectionOption[];
isQueryProvider?: boolean; isQueryProvider?: boolean;
supportedExecutionPlanFileExtensions?: string[]; supportedExecutionPlanFileExtensions?: string[];
connectionStringOptions?: ConnectionStringOptions;
} }
export interface ProviderFeatures { export interface ProviderFeatures {

View File

@@ -40,6 +40,10 @@ export class CapabilitiesService extends Disposable implements ICapabilitiesServ
} }
// By default isQueryProvider is true. // By default isQueryProvider is true.
provider.connection.isQueryProvider = provider.connection.isQueryProvider !== false; provider.connection.isQueryProvider = provider.connection.isQueryProvider !== false;
provider.connection.connectionStringOptions = {
isEnabled: !!(provider.connection.connectionStringOptions?.isEnabled),
isDefault: !!(provider.connection.connectionStringOptions?.isDefault)
};
this._onCapabilitiesRegistered.fire({ id, features: provider }); this._onCapabilitiesRegistered.fire({ id, features: provider });
} }

View File

@@ -193,11 +193,6 @@ configurationRegistry.registerConfiguration({
'description': localize('sql.defaultEngineDescription', "Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection."), 'description': localize('sql.defaultEngineDescription', "Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection."),
'default': 'MSSQL' 'default': 'MSSQL'
}, },
'connection.parseClipboardForConnectionString': {
'type': 'boolean',
'default': true,
'description': localize('connection.parseClipboardForConnectionStringDescription', "Attempt to parse the contents of the clipboard when the connection dialog is opened or a paste is performed.")
},
'connection.showUnsupportedServerVersionWarning': { 'connection.showUnsupportedServerVersionWarning': {
'type': 'boolean', 'type': 'boolean',
'default': true, 'default': true,

View File

@@ -34,6 +34,19 @@ const ConnectionProviderContrib: IJSONSchema = {
type: 'boolean', type: 'boolean',
description: localize('schema.isQueryProvider', "Whether the provider is also a query provider. The default value is true.") description: localize('schema.isQueryProvider', "Whether the provider is also a query provider. The default value is true.")
}, },
connectionStringOptions: {
type: 'object',
properties: {
isEnabled: {
type: 'boolean',
description: localize('schema.enableConnectionStringOption', "Whether the provider supports connection string as an input option. The default value is false.")
},
isDefaultOption: {
type: 'boolean',
description: localize('schema.useConnectionStringAsDefaultOption', "Whether the connection provider uses connection string as the default option to connect. The default value is false.")
}
},
},
iconPath: { iconPath: {
description: localize('schema.iconPath', "Icon path for the server type"), description: localize('schema.iconPath', "Icon path for the server type"),
oneOf: [ oneOf: [

View File

@@ -13,7 +13,6 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as Constants from 'sql/platform/connection/common/constants'; import * as Constants from 'sql/platform/connection/common/constants';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import * as styler from 'sql/platform/theme/common/styler'; import * as styler from 'sql/platform/theme/common/styler';
import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces'; import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces';
@@ -23,11 +22,10 @@ import { localize } from 'vs/nls';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { OS, OperatingSystem } from 'vs/base/common/platform'; import { OS, OperatingSystem } from 'vs/base/common/platform';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { ConnectionWidget, AuthenticationType } from 'sql/workbench/services/connection/browser/connectionWidget'; import { ConnectionWidget, AuthenticationType } from 'sql/workbench/services/connection/browser/connectionWidget';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
/** /**
* Connection Widget clas for CMS Connections * Connection Widget clas for CMS Connections
@@ -46,14 +44,11 @@ export class CmsConnectionWidget extends ConnectionWidget {
@IContextViewService _contextViewService: IContextViewService, @IContextViewService _contextViewService: IContextViewService,
@ILayoutService _layoutService: ILayoutService, @ILayoutService _layoutService: ILayoutService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService, @IConnectionManagementService _connectionManagementService: IConnectionManagementService,
@ICapabilitiesService _capabilitiesService: ICapabilitiesService,
@IClipboardService _clipboardService: IClipboardService,
@IConfigurationService _configurationService: IConfigurationService,
@IAccountManagementService _accountManagementService: IAccountManagementService, @IAccountManagementService _accountManagementService: IAccountManagementService,
@ILogService _logService: ILogService, @ILogService _logService: ILogService,
@IErrorMessageService _errorMessageService: IErrorMessageService,
) { ) {
super(options, callbacks, providerName, _themeService, _contextViewService, _connectionManagementService, _capabilitiesService, super(options, callbacks, providerName, _themeService, _contextViewService, _connectionManagementService, _accountManagementService, _logService, _errorMessageService);
_clipboardService, _configurationService, _accountManagementService, _logService);
let authTypeOption = this._optionsMaps[ConnectionOptionSpecialType.authType]; let authTypeOption = this._optionsMaps[ConnectionOptionSpecialType.authType];
if (authTypeOption) { if (authTypeOption) {
let authTypeDefault = this.getAuthTypeDefault(authTypeOption, OS); let authTypeDefault = this.getAuthTypeDefault(authTypeOption, OS);
@@ -132,10 +127,6 @@ export class CmsConnectionWidget extends ConnectionWidget {
if (this._authTypeSelectBox) { if (this._authTypeSelectBox) {
this.onAuthTypeSelected(this._authTypeSelectBox.value); this.onAuthTypeSelected(this._authTypeSelectBox.value);
} }
DOM.addDisposableListener(container, 'paste', e => {
this._handleClipboard().catch(err => this._logService.error(`Unexpected error parsing clipboard contents for CMS Connection Dialog ${err}`));
});
} }
public override handleOnConnecting(): void { public override handleOnConnecting(): void {
@@ -156,8 +147,8 @@ export class CmsConnectionWidget extends ConnectionWidget {
return this._serverDescriptionInputBox.value; return this._serverDescriptionInputBox.value;
} }
public override connect(model: IConnectionProfile): boolean { public override async connect(model: IConnectionProfile): Promise<boolean> {
let validInputs = super.connect(model); let validInputs = await super.connect(model);
if (this._serverDescriptionInputBox) { if (this._serverDescriptionInputBox) {
model.options.registeredServerDescription = this._serverDescriptionInputBox.value; model.options.registeredServerDescription = this._serverDescriptionInputBox.value;
model.options.registeredServerName = this._connectionNameInputBox.value; model.options.registeredServerName = this._connectionNameInputBox.value;

View File

@@ -177,8 +177,8 @@ export class ConnectionController implements IConnectionComponentController {
this._connectionWidget.focusOnOpen(); this._connectionWidget.focusOnOpen();
} }
public validateConnection(): IConnectionValidateResult { public async validateConnection(): Promise<IConnectionValidateResult> {
return { isValid: this._connectionWidget.connect(this._model), connection: this._model }; return { isValid: await this._connectionWidget.connect(this._model), connection: this._model };
} }
public fillInConnectionInputs(connectionInfo: IConnectionProfile): void { public fillInConnectionInputs(connectionInfo: IConnectionProfile): void {

View File

@@ -49,7 +49,7 @@ export interface IConnectionComponentCallbacks {
export interface IConnectionComponentController { export interface IConnectionComponentController {
showUiComponent(container: HTMLElement, didChange?: boolean): void; showUiComponent(container: HTMLElement, didChange?: boolean): void;
initDialog(providers: string[], model: IConnectionProfile): void; initDialog(providers: string[], model: IConnectionProfile): void;
validateConnection(): IConnectionValidateResult; validateConnection(): Promise<IConnectionValidateResult>;
fillInConnectionInputs(connectionInfo: IConnectionProfile): void; fillInConnectionInputs(connectionInfo: IConnectionProfile): void;
handleOnConnecting(): void; handleOnConnecting(): void;
handleResetConnection(): void; handleResetConnection(): void;
@@ -162,14 +162,14 @@ export class ConnectionDialogService implements IConnectionDialogService {
} }
private handleOnConnect(params: INewConnectionParams, profile?: IConnectionProfile): void { private async handleOnConnect(params: INewConnectionParams, profile?: IConnectionProfile): Promise<void> {
this._logService.debug('ConnectionDialogService: onConnect event is received'); this._logService.debug('ConnectionDialogService: onConnect event is received');
if (!this._connecting) { if (!this._connecting) {
this._logService.debug('ConnectionDialogService: Start connecting'); this._logService.debug('ConnectionDialogService: Start connecting');
this._connecting = true; this._connecting = true;
this.handleProviderOnConnecting(); this.handleProviderOnConnecting();
if (!profile) { if (!profile) {
let result = this.uiController.validateConnection(); let result = await this.uiController.validateConnection();
if (!result.isValid) { if (!result.isValid) {
this._logService.debug('ConnectionDialogService: Connection is invalid'); this._logService.debug('ConnectionDialogService: Connection is invalid');
this._connecting = false; this._connecting = false;
@@ -472,8 +472,8 @@ export class ConnectionDialogService implements IConnectionDialogService {
this._connectionDialog.databaseDropdownExpanded = this.uiController.databaseDropdownExpanded; this._connectionDialog.databaseDropdownExpanded = this.uiController.databaseDropdownExpanded;
this.handleOnCancel(this._connectionDialog.newConnectionParams); this.handleOnCancel(this._connectionDialog.newConnectionParams);
}); });
this._connectionDialog.onConnect((profile) => { this._connectionDialog.onConnect(async (profile) => {
this.handleOnConnect(this._connectionDialog.newConnectionParams, profile as IConnectionProfile); await this.handleOnConnect(this._connectionDialog.newConnectionParams, profile as IConnectionProfile);
}); });
this._connectionDialog.onShowUiComponent((input) => this.handleShowUiComponent(input)); this._connectionDialog.onShowUiComponent((input) => this.handleShowUiComponent(input));
this._connectionDialog.onInitDialog(() => this.handleInitDialog()); this._connectionDialog.onInitDialog(() => this.handleInitDialog());

View File

@@ -15,8 +15,6 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import * as styler from 'sql/platform/theme/common/styler'; import * as styler from 'sql/platform/theme/common/styler';
import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces'; import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces';
@@ -28,12 +26,15 @@ import { localize } from 'vs/nls';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { OS, OperatingSystem } from 'vs/base/common/platform'; import { OS, OperatingSystem } from 'vs/base/common/platform';
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown'; import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
import { RadioButton } from 'sql/base/browser/ui/radioButton/radioButton';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import Severity from 'vs/base/common/severity';
import { ConnectionStringOptions } from 'sql/platform/capabilities/common/capabilitiesService';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
export enum AuthenticationType { export enum AuthenticationType {
SqlLogin = 'SqlLogin', SqlLogin = 'SqlLogin',
@@ -44,9 +45,14 @@ export enum AuthenticationType {
None = 'None' // Kusto supports no authentication None = 'None' // Kusto supports no authentication
} }
const ConnectionStringText = localize('connectionWidget.connectionString', "Connection string");
export class ConnectionWidget extends lifecycle.Disposable { export class ConnectionWidget extends lifecycle.Disposable {
private _defaultInputOptionRadioButton: RadioButton;
private _connectionStringRadioButton: RadioButton;
private _previousGroupOption: string; private _previousGroupOption: string;
private _serverGroupOptions: IConnectionProfileGroup[]; private _serverGroupOptions: IConnectionProfileGroup[];
private _connectionStringInputBox: InputBox;
private _serverNameInputBox: InputBox; private _serverNameInputBox: InputBox;
private _userNameInputBox: InputBox; private _userNameInputBox: InputBox;
private _passwordInputBox: InputBox; private _passwordInputBox: InputBox;
@@ -66,6 +72,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
private _loadingDatabaseName: string = localize('loadingDatabaseOption', "Loading..."); private _loadingDatabaseName: string = localize('loadingDatabaseOption', "Loading...");
private _serverGroupDisplayString: string = localize('serverGroup', "Server group"); private _serverGroupDisplayString: string = localize('serverGroup', "Server group");
private _token: string; private _token: string;
private _connectionStringOptions: ConnectionStringOptions;
protected _container: HTMLElement; protected _container: HTMLElement;
protected _serverGroupSelectBox: SelectBox; protected _serverGroupSelectBox: SelectBox;
protected _authTypeSelectBox: SelectBox; protected _authTypeSelectBox: SelectBox;
@@ -110,11 +117,9 @@ export class ConnectionWidget extends lifecycle.Disposable {
@IThemeService protected _themeService: IThemeService, @IThemeService protected _themeService: IThemeService,
@IContextViewService protected _contextViewService: IContextViewService, @IContextViewService protected _contextViewService: IContextViewService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IClipboardService private _clipboardService: IClipboardService,
@IConfigurationService private _configurationService: IConfigurationService,
@IAccountManagementService private _accountManagementService: IAccountManagementService, @IAccountManagementService private _accountManagementService: IAccountManagementService,
@ILogService protected _logService: ILogService, @ILogService protected _logService: ILogService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) { ) {
super(); super();
this._callbacks = callbacks; this._callbacks = callbacks;
@@ -129,8 +134,10 @@ export class ConnectionWidget extends lifecycle.Disposable {
let authTypeDefault = this.getAuthTypeDefault(authTypeOption, OS); let authTypeDefault = this.getAuthTypeDefault(authTypeOption, OS);
let authTypeDefaultDisplay = this.getAuthTypeDisplayName(authTypeDefault); let authTypeDefaultDisplay = this.getAuthTypeDisplayName(authTypeDefault);
this._authTypeSelectBox = new SelectBox(authTypeOption.categoryValues.map(c => c.displayName), authTypeDefaultDisplay, this._contextViewService, undefined, { ariaLabel: authTypeOption.displayName }); this._authTypeSelectBox = new SelectBox(authTypeOption.categoryValues.map(c => c.displayName), authTypeDefaultDisplay, this._contextViewService, undefined, { ariaLabel: authTypeOption.displayName });
this._register(this._authTypeSelectBox);
} }
this._providerName = providerName; this._providerName = providerName;
this._connectionStringOptions = this._connectionManagementService.getProviderProperties(this._providerName).connectionStringOptions;
} }
protected getAuthTypeDefault(option: azdata.ConnectionOption, os: OperatingSystem): string { protected getAuthTypeDefault(option: azdata.ConnectionOption, os: OperatingSystem): string {
@@ -159,6 +166,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
public createConnectionWidget(container: HTMLElement, authTypeChanged: boolean = false): void { public createConnectionWidget(container: HTMLElement, authTypeChanged: boolean = false): void {
this._serverGroupOptions = [this.DefaultServerGroup]; this._serverGroupOptions = [this.DefaultServerGroup];
this._serverGroupSelectBox = new SelectBox(this._serverGroupOptions.map(g => g.name), this.DefaultServerGroup.name, this._contextViewService, undefined, { ariaLabel: this._serverGroupDisplayString }); this._serverGroupSelectBox = new SelectBox(this._serverGroupOptions.map(g => g.name), this.DefaultServerGroup.name, this._contextViewService, undefined, { ariaLabel: this._serverGroupDisplayString });
this._register(this._serverGroupSelectBox);
this._previousGroupOption = this._serverGroupSelectBox.value; this._previousGroupOption = this._serverGroupSelectBox.value;
this._container = DOM.append(container, DOM.$('div.connection-table')); this._container = DOM.append(container, DOM.$('div.connection-table'));
this._tableContainer = DOM.append(this._container, DOM.$('table.connection-table-content')); this._tableContainer = DOM.append(this._container, DOM.$('table.connection-table-content'));
@@ -168,53 +176,77 @@ export class ConnectionWidget extends lifecycle.Disposable {
if (this._authTypeSelectBox) { if (this._authTypeSelectBox) {
this.onAuthTypeSelected(this._authTypeSelectBox.value); this.onAuthTypeSelected(this._authTypeSelectBox.value);
} }
DOM.addDisposableListener(container, 'paste', e => {
this._handleClipboard().catch(err => this._logService.error(`Unexpected error parsing clipboard contents for connection widget : ${err}`));
});
}
protected async _handleClipboard(): Promise<void> {
if (this._configurationService.getValue<boolean>('connection.parseClipboardForConnectionString')) {
let paste = await this._clipboardService.readText();
this._connectionManagementService.buildConnectionInfo(paste, this._providerName).then(e => {
if (e) {
let profile = new ConnectionProfile(this._capabilitiesService, this._providerName);
profile.options = e.options;
if (profile.serverName) {
this.initDialog(profile);
}
}
});
}
} }
protected fillInConnectionForm(authTypeChanged: boolean = false): void { protected fillInConnectionForm(authTypeChanged: boolean = false): void {
// Server Name this.addInputOptionRadioButtons();
this.addConnectionStringInput();
this.addServerNameOption(); this.addServerNameOption();
// Authentication type
this.addAuthenticationTypeOption(authTypeChanged); this.addAuthenticationTypeOption(authTypeChanged);
// Login Options
this.addLoginOptions(); this.addLoginOptions();
// Database
this.addDatabaseOption(); this.addDatabaseOption();
// Server Group
this.addServerGroupOption(); this.addServerGroupOption();
// Connection Name
this.addConnectionNameOptions(); this.addConnectionNameOptions();
// Advanced Options
this.addAdvancedOptions(); this.addAdvancedOptions();
this.updateRequiredStateForOptions();
if (this._connectionStringOptions.isEnabled) {
// update the UI based on connection string setting after initialization
this.handleConnectionStringOptionChange();
}
}
private validateRequiredOptionValue(value: string, optionName: string): IMessage | undefined {
return isFalsyOrWhitespace(value) ? ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', "{0} is required.", optionName) }) : undefined;
}
private addInputOptionRadioButtons(): void {
if (this._connectionStringOptions.isEnabled) {
const groupName = 'input-option-type';
const inputOptionsContainer = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', 'connection-input-options');
this._defaultInputOptionRadioButton = new RadioButton(inputOptionsContainer, { label: 'Parameters', checked: !this._connectionStringOptions.isDefault });
this._connectionStringRadioButton = new RadioButton(inputOptionsContainer, { label: 'Connection String', checked: this._connectionStringOptions.isDefault });
this._defaultInputOptionRadioButton.name = groupName;
this._connectionStringRadioButton.name = groupName;
this._register(this._defaultInputOptionRadioButton);
this._register(this._connectionStringRadioButton);
this._register(this._defaultInputOptionRadioButton.onDidChangeCheckedState(() => {
this.handleConnectionStringOptionChange();
}));
}
}
private addConnectionStringInput(): void {
if (this._connectionStringOptions.isEnabled) {
const connectionStringContainer = DialogHelper.appendRow(this._tableContainer, ConnectionStringText, 'connection-label', 'connection-input', 'connection-string-row', true);
this._connectionStringInputBox = new InputBox(connectionStringContainer, this._contextViewService, {
validationOptions: {
validation: (value: string) => {
return this.validateRequiredOptionValue(value, ConnectionStringText);
}
},
ariaLabel: ConnectionStringText,
flexibleHeight: true,
flexibleMaxHeight: 100
});
this._register(this._connectionStringInputBox);
this._register(this._connectionStringInputBox.onDidChange(() => {
this.setConnectButton();
}));
}
}
private updateRequiredStateForOptions(): void {
if (this._connectionStringInputBox) {
this._connectionStringInputBox.required = this.useConnectionString;
}
const userNameOption: azdata.ConnectionOption = this._optionsMaps[ConnectionOptionSpecialType.userName];
this._serverNameInputBox.required = !this.useConnectionString;
this._userNameInputBox.required = (!this.useConnectionString) && userNameOption?.isRequired;
} }
protected addAuthenticationTypeOption(authTypeChanged: boolean = false): void { protected addAuthenticationTypeOption(authTypeChanged: boolean = false): void {
if (this._optionsMaps[ConnectionOptionSpecialType.authType]) { if (this._optionsMaps[ConnectionOptionSpecialType.authType]) {
let authType = DialogHelper.appendRow(this._tableContainer, this._optionsMaps[ConnectionOptionSpecialType.authType].displayName, 'connection-label', 'connection-input'); let authType = DialogHelper.appendRow(this._tableContainer, this._optionsMaps[ConnectionOptionSpecialType.authType].displayName, 'connection-label', 'connection-input', 'auth-type-row');
DialogHelper.appendInputSelectBox(authType, this._authTypeSelectBox); DialogHelper.appendInputSelectBox(authType, this._authTypeSelectBox);
} }
} }
@@ -222,21 +254,16 @@ export class ConnectionWidget extends lifecycle.Disposable {
protected addServerNameOption(): void { protected addServerNameOption(): void {
// Server name // Server name
let serverNameOption = this._optionsMaps[ConnectionOptionSpecialType.serverName]; let serverNameOption = this._optionsMaps[ConnectionOptionSpecialType.serverName];
let serverName = DialogHelper.appendRow(this._tableContainer, serverNameOption.displayName, 'connection-label', 'connection-input', undefined, true); let serverName = DialogHelper.appendRow(this._tableContainer, serverNameOption.displayName, 'connection-label', 'connection-input', 'server-name-row', true);
this._serverNameInputBox = new InputBox(serverName, this._contextViewService, { this._serverNameInputBox = new InputBox(serverName, this._contextViewService, {
validationOptions: { validationOptions: {
validation: (value: string) => { validation: (value: string) => {
if (!value) { return this.validateRequiredOptionValue(value, serverNameOption.displayName);
return ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', "{0} is required.", serverNameOption.displayName) });
} else if (value.startsWith(' ') || value.endsWith(' ')) {
return ({ type: MessageType.WARNING, content: localize('connectionWidget.fieldWillBeTrimmed', "{0} will be trimmed.", serverNameOption.displayName) });
}
return undefined;
} }
}, },
ariaLabel: serverNameOption.displayName, ariaLabel: serverNameOption.displayName
required: true
}); });
this._register(this._serverNameInputBox);
} }
protected addLoginOptions(): void { protected addLoginOptions(): void {
@@ -248,23 +275,26 @@ export class ConnectionWidget extends lifecycle.Disposable {
validationOptions: { validationOptions: {
validation: (value: string) => self.validateUsername(value, userNameOption.isRequired) ? ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', "{0} is required.", userNameOption.displayName) }) : null validation: (value: string) => self.validateUsername(value, userNameOption.isRequired) ? ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', "{0} is required.", userNameOption.displayName) }) : null
}, },
ariaLabel: userNameOption.displayName, ariaLabel: userNameOption.displayName
required: userNameOption.isRequired
}); });
this._register(this._userNameInputBox);
// Password // Password
let passwordOption = this._optionsMaps[ConnectionOptionSpecialType.password]; let passwordOption = this._optionsMaps[ConnectionOptionSpecialType.password];
let password = DialogHelper.appendRow(this._tableContainer, passwordOption.displayName, 'connection-label', 'connection-input', 'password-row'); let password = DialogHelper.appendRow(this._tableContainer, passwordOption.displayName, 'connection-label', 'connection-input', 'password-row');
this._passwordInputBox = new InputBox(password, this._contextViewService, { ariaLabel: passwordOption.displayName }); this._passwordInputBox = new InputBox(password, this._contextViewService, { ariaLabel: passwordOption.displayName });
this._passwordInputBox.inputElement.type = 'password'; this._passwordInputBox.inputElement.type = 'password';
this._register(this._passwordInputBox);
// Remember password // Remember password
let rememberPasswordLabel = localize('rememberPassword', "Remember password"); let rememberPasswordLabel = localize('rememberPassword', "Remember password");
this._rememberPasswordCheckBox = this.appendCheckbox(this._tableContainer, rememberPasswordLabel, 'connection-input', 'password-row', false); this._rememberPasswordCheckBox = this.appendCheckbox(this._tableContainer, rememberPasswordLabel, 'connection-input', 'password-row', false);
this._register(this._rememberPasswordCheckBox);
// Azure account picker // Azure account picker
let accountLabel = localize('connection.azureAccountDropdownLabel', "Account"); let accountLabel = localize('connection.azureAccountDropdownLabel', "Account");
let accountDropdown = DialogHelper.appendRow(this._tableContainer, accountLabel, 'connection-label', 'connection-input', 'azure-account-row'); let accountDropdown = DialogHelper.appendRow(this._tableContainer, accountLabel, 'connection-label', 'connection-input', 'azure-account-row');
this._azureAccountDropdown = new SelectBox([], undefined, this._contextViewService, accountDropdown, { ariaLabel: accountLabel }); this._azureAccountDropdown = new SelectBox([], undefined, this._contextViewService, accountDropdown, { ariaLabel: accountLabel });
this._register(this._azureAccountDropdown);
DialogHelper.appendInputSelectBox(accountDropdown, this._azureAccountDropdown); DialogHelper.appendInputSelectBox(accountDropdown, this._azureAccountDropdown);
let refreshCredentials = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', ['azure-account-row', 'refresh-credentials-link']); let refreshCredentials = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', ['azure-account-row', 'refresh-credentials-link']);
this._refreshCredentialsLink = DOM.append(refreshCredentials, DOM.$('a')); this._refreshCredentialsLink = DOM.append(refreshCredentials, DOM.$('a'));
@@ -274,6 +304,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
let tenantLabel = localize('connection.azureTenantDropdownLabel', "Azure AD tenant"); let tenantLabel = localize('connection.azureTenantDropdownLabel', "Azure AD tenant");
let tenantDropdown = DialogHelper.appendRow(this._tableContainer, tenantLabel, 'connection-label', 'connection-input', ['azure-account-row', 'azure-tenant-row']); let tenantDropdown = DialogHelper.appendRow(this._tableContainer, tenantLabel, 'connection-label', 'connection-input', ['azure-account-row', 'azure-tenant-row']);
this._azureTenantDropdown = new SelectBox([], undefined, this._contextViewService, tenantDropdown, { ariaLabel: tenantLabel }); this._azureTenantDropdown = new SelectBox([], undefined, this._contextViewService, tenantDropdown, { ariaLabel: tenantLabel });
this._register(this._azureTenantDropdown);
DialogHelper.appendInputSelectBox(tenantDropdown, this._azureTenantDropdown); DialogHelper.appendInputSelectBox(tenantDropdown, this._azureTenantDropdown);
} }
@@ -281,7 +312,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
// Database // Database
let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName]; let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName];
if (databaseOption) { if (databaseOption) {
let databaseName = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input'); let databaseName = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input', 'database-row');
this._databaseNameInputBox = new Dropdown(databaseName, this._contextViewService, { this._databaseNameInputBox = new Dropdown(databaseName, this._contextViewService, {
values: [this._defaultDatabaseName, this._loadingDatabaseName], values: [this._defaultDatabaseName, this._loadingDatabaseName],
strictSelection: false, strictSelection: false,
@@ -289,6 +320,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
maxHeight: 125, maxHeight: 125,
ariaLabel: databaseOption.displayName ariaLabel: databaseOption.displayName
}); });
this._register(this._databaseNameInputBox);
} }
} }
@@ -306,11 +338,34 @@ export class ConnectionWidget extends lifecycle.Disposable {
connectionNameOption.displayName = localize('connectionName', "Name (optional)"); connectionNameOption.displayName = localize('connectionName', "Name (optional)");
let connectionNameBuilder = DialogHelper.appendRow(this._tableContainer, connectionNameOption.displayName, 'connection-label', 'connection-input'); let connectionNameBuilder = DialogHelper.appendRow(this._tableContainer, connectionNameOption.displayName, 'connection-label', 'connection-input');
this._connectionNameInputBox = new InputBox(connectionNameBuilder, this._contextViewService, { ariaLabel: connectionNameOption.displayName }); this._connectionNameInputBox = new InputBox(connectionNameBuilder, this._contextViewService, { ariaLabel: connectionNameOption.displayName });
this._register(this._connectionNameInputBox);
} }
protected addAdvancedOptions(): void { protected addAdvancedOptions(): void {
let AdvancedLabel = localize('advanced', "Advanced..."); const rowContainer = DOM.append(this._tableContainer, DOM.$('tr.advanced-options-row'));
this._advancedButton = this.createAdvancedButton(this._tableContainer, AdvancedLabel); DOM.append(rowContainer, DOM.$('td'));
const buttonContainer = DOM.append(rowContainer, DOM.$('td'));
buttonContainer.setAttribute('align', 'right');
const divContainer = DOM.append(buttonContainer, DOM.$('div.advanced-button'));
this._advancedButton = new Button(divContainer, { secondary: true });
this._register(this._advancedButton);
this._advancedButton.label = localize('advanced', "Advanced...");
this._register(this._advancedButton.onDidClick(() => {
//open advanced page
this._callbacks.onAdvancedProperties();
}));
}
private handleConnectionStringOptionChange(): void {
const connectionStringClass = 'use-connection-string';
if (this.useConnectionString) {
this._tableContainer.classList.add(connectionStringClass);
this._connectionStringInputBox.layout();
} else {
this._tableContainer.classList.remove(connectionStringClass);
}
this.updateRequiredStateForOptions();
this.setConnectButton();
} }
private validateUsername(value: string, isOptionRequired: boolean): boolean { private validateUsername(value: string, isOptionRequired: boolean): boolean {
@@ -323,21 +378,6 @@ export class ConnectionWidget extends lifecycle.Disposable {
return false; return false;
} }
protected createAdvancedButton(container: HTMLElement, title: string): Button {
let rowContainer = DOM.append(container, DOM.$('tr'));
DOM.append(rowContainer, DOM.$('td'));
let cellContainer = DOM.append(rowContainer, DOM.$('td'));
cellContainer.setAttribute('align', 'right');
let divContainer = DOM.append(cellContainer, DOM.$('div.advanced-button'));
let button = new Button(divContainer, { secondary: true });
button.label = title;
button.onDidClick(() => {
//open advanced page
this._callbacks.onAdvancedProperties();
});
return button;
}
private appendCheckbox(container: HTMLElement, label: string, cellContainerClass: string, rowContainerClass: string, isChecked: boolean): Checkbox { private appendCheckbox(container: HTMLElement, label: string, cellContainerClass: string, rowContainerClass: string, isChecked: boolean): Checkbox {
let rowContainer = DOM.append(container, DOM.$(`tr.${rowContainerClass}`)); let rowContainer = DOM.append(container, DOM.$(`tr.${rowContainerClass}`));
DOM.append(rowContainer, DOM.$('td')); DOM.append(rowContainer, DOM.$('td'));
@@ -389,6 +429,10 @@ export class ConnectionWidget extends lifecycle.Disposable {
})); }));
} }
if (this._connectionStringInputBox) {
this._register(styler.attachInputBoxStyler(this._connectionStringInputBox, this._themeService));
}
if (this._authTypeSelectBox) { if (this._authTypeSelectBox) {
// Theme styler // Theme styler
this._register(styler.attachSelectBoxStyler(this._authTypeSelectBox, this._themeService)); this._register(styler.attachSelectBoxStyler(this._authTypeSelectBox, this._themeService));
@@ -443,12 +487,14 @@ export class ConnectionWidget extends lifecycle.Disposable {
} }
private setConnectButton(): void { private setConnectButton(): void {
let showUsername: boolean; let shouldEnableConnectButton: boolean;
if (this.authType) { if (this.useConnectionString) {
showUsername = this.authType === AuthenticationType.SqlLogin || this.authType === AuthenticationType.AzureMFAAndUser; shouldEnableConnectButton = this._connectionStringInputBox.isInputValid();
} else {
const showUsername: boolean = this.authType && (this.authType === AuthenticationType.SqlLogin || this.authType === AuthenticationType.AzureMFAAndUser);
shouldEnableConnectButton = showUsername ? (this._serverNameInputBox.isInputValid() && this._userNameInputBox.isInputValid()) : this._serverNameInputBox.isInputValid();
} }
showUsername ? this._callbacks.onSetConnectButton(!!this.serverName && !!this.userName) : this._callbacks.onSetConnectButton(shouldEnableConnectButton);
this._callbacks.onSetConnectButton(!!this.serverName);
} }
protected onAuthTypeSelected(selectedAuthType: string) { protected onAuthTypeSelected(selectedAuthType: string) {
@@ -623,9 +669,12 @@ export class ConnectionWidget extends lifecycle.Disposable {
} }
public focusOnOpen(): void { public focusOnOpen(): void {
this._handleClipboard().catch(err => this._logService.error(`Unexpected error parsing clipboard contents for connection widget : ${err}`)); if (this.useConnectionString) {
this._connectionStringInputBox.focus();
} else {
this._serverNameInputBox.focus(); this._serverNameInputBox.focus();
this.focusPasswordIfNeeded(); this.focusPasswordIfNeeded();
}
this.clearValidationMessages(); this.clearValidationMessages();
} }
@@ -633,6 +682,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
this._serverNameInputBox.hideMessage(); this._serverNameInputBox.hideMessage();
this._userNameInputBox.hideMessage(); this._userNameInputBox.hideMessage();
this._azureAccountDropdown.hideMessage(); this._azureAccountDropdown.hideMessage();
this._connectionStringInputBox?.hideMessage();
} }
private getModelValue(value: string): string { private getModelValue(value: string): string {
@@ -641,6 +691,10 @@ export class ConnectionWidget extends lifecycle.Disposable {
public fillInConnectionInputs(connectionInfo: IConnectionProfile) { public fillInConnectionInputs(connectionInfo: IConnectionProfile) {
if (connectionInfo) { if (connectionInfo) {
// If initializing from an existing connection, always switch to the parameters view.
if (connectionInfo.serverName && this._connectionStringOptions.isEnabled) {
this._defaultInputOptionRadioButton.checked = true;
}
this._serverNameInputBox.value = this.getModelValue(connectionInfo.serverName); this._serverNameInputBox.value = this.getModelValue(connectionInfo.serverName);
this._connectionNameInputBox.value = this.getModelValue(connectionInfo.connectionName); this._connectionNameInputBox.value = this.getModelValue(connectionInfo.connectionName);
this._userNameInputBox.value = this.getModelValue(connectionInfo.userName); this._userNameInputBox.value = this.getModelValue(connectionInfo.userName);
@@ -753,6 +807,12 @@ export class ConnectionWidget extends lifecycle.Disposable {
if (this._authTypeSelectBox) { if (this._authTypeSelectBox) {
this._authTypeSelectBox.disable(); this._authTypeSelectBox.disable();
} }
if (this._connectionStringOptions.isEnabled) {
this._connectionStringInputBox.disable();
this._defaultInputOptionRadioButton.enabled = false;
this._connectionStringRadioButton.enabled = false;
}
} }
public handleResetConnection(): void { public handleResetConnection(): void {
@@ -785,6 +845,19 @@ export class ConnectionWidget extends lifecycle.Disposable {
if (this._databaseNameInputBox) { if (this._databaseNameInputBox) {
this._databaseNameInputBox.enabled = true; this._databaseNameInputBox.enabled = true;
} }
if (this._connectionStringOptions.isEnabled) {
this._connectionStringInputBox.enable();
this._defaultInputOptionRadioButton.enabled = true;
this._connectionStringRadioButton.enabled = true;
}
}
public get useConnectionString(): boolean {
return !!(this._connectionStringRadioButton?.checked);
}
public get connectionString(): string {
return this._connectionStringInputBox?.value;
} }
public get connectionName(): string { public get connectionName(): string {
@@ -843,6 +916,13 @@ export class ConnectionWidget extends lifecycle.Disposable {
} }
private validateInputs(): boolean { private validateInputs(): boolean {
if (this.useConnectionString) {
const isConnectionStringValid = this._connectionStringInputBox.validate() === undefined;
if (!isConnectionStringValid) {
this._connectionStringInputBox.focus();
}
return isConnectionStringValid;
} else {
let isFocused = false; let isFocused = false;
const isServerNameValid = this._serverNameInputBox.validate() === undefined; const isServerNameValid = this._serverNameInputBox.validate() === undefined;
if (!isServerNameValid) { if (!isServerNameValid) {
@@ -866,10 +946,21 @@ export class ConnectionWidget extends lifecycle.Disposable {
} }
return isServerNameValid && isUserNameValid && isPasswordValid && isAzureAccountValid; return isServerNameValid && isUserNameValid && isPasswordValid && isAzureAccountValid;
} }
}
public connect(model: IConnectionProfile): boolean { public async connect(model: IConnectionProfile): Promise<boolean> {
let validInputs = this.validateInputs(); let validInputs = this.validateInputs();
if (validInputs) { if (validInputs) {
if (this.useConnectionString) {
const connInfo = await this._connectionManagementService.buildConnectionInfo(this.connectionString, this._providerName);
if (connInfo) {
model.options = connInfo.options;
model.savePassword = true;
} else {
this._errorMessageService.showDialog(Severity.Error, localize('connectionWidget.Error', "Error"), localize('connectionWidget.ConnectionStringError', "Failed to parse the connection string."));
return false;
}
} else {
model.serverName = this.serverName; model.serverName = this.serverName;
model.userName = this.userName; model.userName = this.userName;
model.password = this.password; model.password = this.password;
@@ -896,6 +987,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
model.azureTenantId = this._azureTenantId; model.azureTenantId = this._azureTenantId;
} }
} }
}
return validInputs; return validInputs;
} }

View File

@@ -49,7 +49,7 @@
} }
.connection-provider-info { .connection-provider-info {
margin: 15px; margin: 0 13px;
} }
.connection-recent-content { .connection-recent-content {
@@ -146,3 +146,23 @@
/* Hide twisties */ /* Hide twisties */
display: none !important; display: none !important;
} }
.connection-dialog .use-connection-string .username-row,
.connection-dialog .use-connection-string .password-row,
.connection-dialog .use-connection-string .server-name-row,
.connection-dialog .use-connection-string .azure-tenant-row,
.connection-dialog .use-connection-string .auth-type-row,
.connection-dialog .use-connection-string .database-row,
.connection-dialog .use-connection-string .advanced-button,
.connection-dialog .connection-string-row {
display: none;
}
.use-connection-string .connection-string-row {
display: table-row;
}
.connection-dialog .connection-input-options .connection-input label {
margin-right: 10px;
}

View File

@@ -52,7 +52,7 @@ import { TestTreeView } from 'sql/workbench/services/connection/test/browser/tes
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService'; import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
import { ConnectionTreeService, IConnectionTreeService } from 'sql/workbench/services/connection/common/connectionTreeService'; import { ConnectionTreeService, IConnectionTreeService } from 'sql/workbench/services/connection/common/connectionTreeService';
import { ConnectionBrowserView } from 'sql/workbench/services/connection/browser/connectionBrowseTab'; import { ConnectionBrowserView } from 'sql/workbench/services/connection/browser/connectionBrowseTab';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { ConnectionProviderProperties, ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
suite('ConnectionDialogService tests', () => { suite('ConnectionDialogService tests', () => {
const testTreeViewId = 'testTreeView'; const testTreeViewId = 'testTreeView';
@@ -115,6 +115,14 @@ suite('ConnectionDialogService tests', () => {
mockConnectionManagementService.setup(x => x.getConnectionGroups(TypeMoq.It.isAny())).returns(() => { mockConnectionManagementService.setup(x => x.getConnectionGroups(TypeMoq.It.isAny())).returns(() => {
return [new ConnectionProfileGroup('test_group', undefined, 'test_group')]; return [new ConnectionProfileGroup('test_group', undefined, 'test_group')];
}); });
mockConnectionManagementService.setup(x => x.getProviderProperties(TypeMoq.It.isAny())).returns(() => {
return <ConnectionProviderProperties>{
connectionStringOptions: {
isEnabled: true,
isDefault: false
}
};
});
testConnectionDialog = new TestConnectionDialogWidget(providerDisplayNames, providerNameToDisplayMap['MSSQL'], providerNameToDisplayMap, testInstantiationService, mockConnectionManagementService.object, undefined, undefined, viewDescriptorService, new TestThemeService(), new TestLayoutService(), new NullAdsTelemetryService(), new MockContextKeyService(), undefined, new NullLogService(), new TestTextResourcePropertiesService(new TestConfigurationService), new TestConfigurationService(), new TestCapabilitiesService()); testConnectionDialog = new TestConnectionDialogWidget(providerDisplayNames, providerNameToDisplayMap['MSSQL'], providerNameToDisplayMap, testInstantiationService, mockConnectionManagementService.object, undefined, undefined, viewDescriptorService, new TestThemeService(), new TestLayoutService(), new NullAdsTelemetryService(), new MockContextKeyService(), undefined, new NullLogService(), new TestTextResourcePropertiesService(new TestConfigurationService), new TestConfigurationService(), new TestCapabilitiesService());
testConnectionDialog.render(); testConnectionDialog.render();
testConnectionDialog['renderBody'](DOM.createStyleSheet()); testConnectionDialog['renderBody'](DOM.createStyleSheet());
@@ -169,11 +177,11 @@ suite('ConnectionDialogService tests', () => {
mockConnectionManagementService.setup(x => x.addSavedPassword(TypeMoq.It.isAny())).returns(() => { mockConnectionManagementService.setup(x => x.addSavedPassword(TypeMoq.It.isAny())).returns(() => {
return Promise.resolve(connectionProfile); return Promise.resolve(connectionProfile);
}); });
mockWidget = TypeMoq.Mock.ofType(ConnectionWidget, TypeMoq.MockBehavior.Strict, [], undefined, 'MSSQL'); mockWidget = TypeMoq.Mock.ofType(ConnectionWidget, TypeMoq.MockBehavior.Strict, [], undefined, 'MSSQL', undefined, undefined, mockConnectionManagementService.object);
mockWidget.setup(x => x.focusOnOpen()); mockWidget.setup(x => x.focusOnOpen());
mockWidget.setup(x => x.handleOnConnecting()); mockWidget.setup(x => x.handleOnConnecting());
mockWidget.setup(x => x.handleResetConnection()); mockWidget.setup(x => x.handleResetConnection());
mockWidget.setup(x => x.connect(TypeMoq.It.isValue(connectionProfile))).returns(() => true); mockWidget.setup(x => x.connect(TypeMoq.It.isValue(connectionProfile))).returns(() => Promise.resolve(true));
mockWidget.setup(x => x.createConnectionWidget(TypeMoq.It.isAny())); mockWidget.setup(x => x.createConnectionWidget(TypeMoq.It.isAny()));
mockWidget.setup(x => x.updateServerGroup(TypeMoq.It.isAny())); mockWidget.setup(x => x.updateServerGroup(TypeMoq.It.isAny()));
mockWidget.setup(x => x.initDialog(TypeMoq.It.isAny())); mockWidget.setup(x => x.initDialog(TypeMoq.It.isAny()));
@@ -313,7 +321,7 @@ suite('ConnectionDialogService tests', () => {
((connectionDialogService as any)._connectionDialog as any).connect(connectionProfile); ((connectionDialogService as any)._connectionDialog as any).connect(connectionProfile);
}); });
assert(called); setTimeout(() => { assert(called); }, 200);
}); });
test('handleOnConnect calls connectAndSaveProfile when called without profile', async () => { test('handleOnConnect calls connectAndSaveProfile when called without profile', async () => {
@@ -325,13 +333,11 @@ suite('ConnectionDialogService tests', () => {
(connectionDialogService as any)._connectionDialog = undefined; (connectionDialogService as any)._connectionDialog = undefined;
(connectionDialogService as any)._dialogDeferredPromise = new Deferred<IConnectionProfile>(); (connectionDialogService as any)._dialogDeferredPromise = new Deferred<IConnectionProfile>();
await connectionDialogService.showDialog(mockConnectionManagementService.object, testConnectionParams, connectionProfile).then(() => { await connectionDialogService.showDialog(mockConnectionManagementService.object, testConnectionParams, connectionProfile);
((connectionDialogService as any)._connectionControllerMap['MSSQL'] as any)._model = connectionProfile; ((connectionDialogService as any)._connectionControllerMap['MSSQL'] as any)._model = connectionProfile;
(connectionDialogService as any)._connectionDialog.connectButtonState = true; (connectionDialogService as any)._connectionDialog.connectButtonState = true;
((connectionDialogService as any)._connectionDialog as any).connect(); ((connectionDialogService as any)._connectionDialog as any).connect();
}); setTimeout(() => { assert(called); }, 200);
assert(called);
}); });
test('handleOnCancel calls cancelEditorConnection', async () => { test('handleOnCancel calls cancelEditorConnection', async () => {