Feature/ext connection dialog (#2201)

* Connection Dialog API for extensions
This commit is contained in:
Leila Lali
2018-08-10 09:29:46 -07:00
committed by GitHub
parent 2a3195636e
commit e5096e61e5
20 changed files with 217 additions and 58 deletions

View File

@@ -59,6 +59,7 @@ export interface IConnectionResult {
errorCode: number;
callStack: string;
errorHandled?: boolean;
connectionProfile?: IConnectionProfile;
}
export interface IConnectionCallbacks {
@@ -130,15 +131,15 @@ export interface IConnectionManagementService {
onConnectionChangedNotification(handle: number, changedConnInfo: sqlops.ChangedConnectionInfo);
getConnectionGroups(): ConnectionProfileGroup[];
getConnectionGroups(providers?: string[]): ConnectionProfileGroup[];
getRecentConnections(): ConnectionProfile[];
getRecentConnections(providers?: string[]): ConnectionProfile[];
clearRecentConnectionsList(): void;
clearRecentConnection(connectionProfile: IConnectionProfile): void;
getActiveConnections(): ConnectionProfile[];
getActiveConnections(providers?: string[]): ConnectionProfile[];
saveProfileGroup(profile: IConnectionProfileGroup): Promise<string>;
@@ -270,7 +271,24 @@ export interface IConnectionManagementService {
export const IConnectionDialogService = createDecorator<IConnectionDialogService>('connectionDialogService');
export interface IConnectionDialogService {
_serviceBrand: any;
/**
* Opens the connection dialog and returns the promise for successfully opening the dialog
* @param connectionManagementService
* @param params
* @param model
* @param connectionResult
*/
showDialog(connectionManagementService: IConnectionManagementService, params: INewConnectionParams, model: IConnectionProfile, connectionResult?: IConnectionResult): Thenable<void>;
/**
* Opens the connection dialog and returns the promise when connection is made
* or dialog is closed
* @param connectionManagementService
* @param params
* @param model
* @param connectionResult
*/
openDialogAndWait(connectionManagementService: IConnectionManagementService, params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): Thenable<IConnectionProfile>;
}
export interface IServerGroupDialogCallbacks {
@@ -304,6 +322,7 @@ export interface INewConnectionParams {
runQueryOnCompletion?: RunQueryOnConnectionMode;
querySelection?: sqlops.ISelectionData;
showDashboard?: boolean;
providers?: string[];
}
export interface IConnectableInput {

View File

@@ -627,12 +627,12 @@ export class ConnectionManagementService extends Disposable implements IConnecti
}
}
public getConnectionGroups(): ConnectionProfileGroup[] {
return this._connectionStore.getConnectionProfileGroups();
public getConnectionGroups(providers?: string[]): ConnectionProfileGroup[] {
return this._connectionStore.getConnectionProfileGroups(false, providers);
}
public getRecentConnections(): ConnectionProfile[] {
return this._connectionStore.getRecentlyUsedConnections();
public getRecentConnections(providers?: string[]): ConnectionProfile[] {
return this._connectionStore.getRecentlyUsedConnections(providers);
}
@@ -644,7 +644,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
this._connectionStore.removeConnectionToMemento(connectionProfile, Constants.recentConnections);
}
public getActiveConnections(): ConnectionProfile[] {
public getActiveConnections(providers?: string[]): ConnectionProfile[] {
return this._connectionStatusManager.getActiveConnectionProfiles();
}
@@ -1002,14 +1002,14 @@ export class ConnectionManagementService extends Disposable implements IConnecti
let connectionMngInfo = this._connectionStatusManager.findConnection(uri);
if (connectionMngInfo && connectionMngInfo.deleted) {
this._connectionStatusManager.deleteConnection(uri);
resolve({ connected: connectResult, errorMessage: undefined, errorCode: undefined, callStack: undefined, errorHandled: true });
resolve({ connected: connectResult, errorMessage: undefined, errorCode: undefined, callStack: undefined, errorHandled: true, connectionProfile: connection });
} else {
if (errorMessage) {
// Connection to the server failed
this._connectionStatusManager.deleteConnection(uri);
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack });
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack, connectionProfile: connection });
} else {
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack });
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack, connectionProfile: connection });
}
}
});

View File

@@ -193,9 +193,14 @@ export class ConnectionStatusManager {
/**
* Get a list of the active connection profiles managed by the status manager
*/
public getActiveConnectionProfiles(): ConnectionProfile[] {
public getActiveConnectionProfiles(providers?: string[]): ConnectionProfile[] {
let profiles = Object.values(this._connections).map((connectionInfo: ConnectionManagementInfo) => connectionInfo.connectionProfile);
// Remove duplicate profiles that may be listed multiple times under different URIs by filtering for profiles that don't have the same ID as an earlier profile in the list
return profiles.filter((profile, index) => profiles.findIndex(otherProfile => otherProfile.id === profile.id) === index);
profiles = profiles.filter((profile, index) => profiles.findIndex(otherProfile => otherProfile.id === profile.id) === index);
if (providers) {
profiles = profiles.filter(f => providers.includes(f.providerName));
}
return profiles;
}
}

View File

@@ -213,13 +213,16 @@ export class ConnectionStore {
*
* @returns {sqlops.ConnectionInfo} the array of connections, empty if none are found
*/
public getRecentlyUsedConnections(): ConnectionProfile[] {
public getRecentlyUsedConnections(providers?: string[]): ConnectionProfile[] {
let configValues: IConnectionProfile[] = this._memento[Constants.recentConnections];
if (!configValues) {
configValues = [];
}
configValues = configValues.filter(c => !!(c));
if (providers && providers.length > 0) {
configValues = configValues.filter(c => providers.includes(c.providerName));
}
return this.convertConfigValuesToConnectionProfiles(configValues);
}
@@ -429,10 +432,13 @@ export class ConnectionStore {
});
}
public getConnectionProfileGroups(withoutConnections?: boolean): ConnectionProfileGroup[] {
public getConnectionProfileGroups(withoutConnections?: boolean, providers?: string[]): ConnectionProfileGroup[] {
let profilesInConfiguration: ConnectionProfile[];
if (!withoutConnections) {
profilesInConfiguration = this._connectionConfig.getConnections(true);
if (providers && providers.length > 0) {
profilesInConfiguration = profilesInConfiguration.filter(x => providers.includes(x.providerName));
}
}
let groups = this._connectionConfig.getAllGroups();

View File

@@ -132,8 +132,8 @@ export class ConnectionController implements IConnectionComponentController {
}
}
private getAllServerGroups(): IConnectionProfileGroup[] {
var connectionGroupRoot = this._connectionManagementService.getConnectionGroups();
private getAllServerGroups(providers?: string[]): IConnectionProfileGroup[] {
var connectionGroupRoot = this._connectionManagementService.getConnectionGroups(providers);
var connectionGroupNames: IConnectionProfileGroup[] = [];
if (connectionGroupRoot && connectionGroupRoot.length > 0) {
this.getServerGroupHelper(connectionGroupRoot[0], connectionGroupNames);
@@ -149,8 +149,8 @@ export class ConnectionController implements IConnectionComponentController {
return connectionGroupNames;
}
public initDialog(connectionInfo: IConnectionProfile): void {
this._connectionWidget.updateServerGroup(this.getAllServerGroups());
public initDialog(providers: string[], connectionInfo: IConnectionProfile): void {
this._connectionWidget.updateServerGroup(this.getAllServerGroups(providers));
this._model = connectionInfo;
this._model.providerName = this._providerName;
let appNameOption = this._providerOptions.find(option => option.specialValueType === ConnectionOptionSpecialType.appName);

View File

@@ -33,6 +33,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { ICommandService } from 'vs/platform/commands/common/commands';
import * as types from 'vs/base/common/types';
import { trim } from 'vs/base/common/strings';
import { Deferred } from 'sql/base/common/promise';
export interface IConnectionValidateResult {
isValid: boolean;
@@ -49,7 +50,7 @@ export interface IConnectionComponentCallbacks {
export interface IConnectionComponentController {
showUiComponent(container: HTMLElement): void;
initDialog(model: IConnectionProfile): void;
initDialog(providers: string[], model: IConnectionProfile): void;
validateConnection(): IConnectionValidateResult;
fillInConnectionInputs(connectionInfo: IConnectionProfile): void;
handleOnConnecting(): void;
@@ -75,6 +76,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
private _currentProviderType: string = 'Microsoft SQL Server';
private _connecting: boolean = false;
private _connectionErrorTitle = localize('connectionError', 'Connection error');
private _dialogDeferredPromise: Deferred<IConnectionProfile>;
constructor(
@IPartService private _partService: IPartService,
@@ -82,17 +84,24 @@ export class ConnectionDialogService implements IConnectionDialogService {
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
@IWindowsService private _windowsService: IWindowsService,
@IClipboardService private _clipboardService: IClipboardService,
@ICommandService private _commandService: ICommandService
) { }
private getDefaultProviderName() {
if (this._workspaceConfigurationService) {
let defaultProvider = WorkbenchUtils.getSqlConfigValue<string>(this._workspaceConfigurationService, Constants.defaultEngine);
let defaultProvider: string;
if (this._providerNameToDisplayNameMap) {
let keys = Object.keys(this._providerNameToDisplayNameMap);
if (keys && keys.length > 0) {
defaultProvider = keys[0];
}
}
if (!defaultProvider && this._workspaceConfigurationService) {
defaultProvider = WorkbenchUtils.getSqlConfigValue<string>(this._workspaceConfigurationService, Constants.defaultEngine);
}
// as a fallback, default to MSSQL if the value from settings is not available
return Constants.mssqlProviderName;
return defaultProvider || Constants.mssqlProviderName;
}
private handleOnConnect(params: INewConnectionParams, profile?: IConnectionProfile): void {
@@ -148,26 +157,28 @@ export class ConnectionDialogService implements IConnectionDialogService {
this._connecting = false;
}
this.uiController.databaseDropdownExpanded = false;
this._dialogDeferredPromise.resolve(undefined);
}
private handleDefaultOnConnect(params: INewConnectionParams, connection: IConnectionProfile): Thenable<void> {
let fromEditor = params && params.connectionType === ConnectionType.editor;
let uri: string = undefined;
if (fromEditor && params.input) {
if (fromEditor && params && params.input) {
uri = params.input.uri;
}
let options: IConnectionCompletionOptions = {
params: params,
saveTheConnection: !fromEditor,
showDashboard: params.showDashboard !== undefined ? params.showDashboard : !fromEditor,
showDashboard: params && params.showDashboard !== undefined ? params.showDashboard : !fromEditor,
showConnectionDialogOnError: false,
showFirewallRuleOnError: true
};
return this._connectionManagementService.connectAndSaveProfile(connection, uri, options, params.input).then(connectionResult => {
return this._connectionManagementService.connectAndSaveProfile(connection, uri, options, params && params.input).then(connectionResult => {
this._connecting = false;
if (connectionResult && connectionResult.connected) {
this._connectionDialog.close();
this._dialogDeferredPromise.resolve(connectionResult.connectionProfile);
} else if (connectionResult && connectionResult.errorHandled) {
this._connectionDialog.resetConnection();
} else {
@@ -213,7 +224,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
}
private handleInitDialog() {
this.uiController.initDialog(this._model);
this.uiController.initDialog(this._params && this._params.providers, this._model);
}
private handleFillInConnectionInputs(connectionInfo: IConnectionProfile): void {
@@ -265,9 +276,25 @@ export class ConnectionDialogService implements IConnectionDialogService {
});
}
public openDialogAndWait(connectionManagementService: IConnectionManagementService,
params?: INewConnectionParams,
model?: IConnectionProfile,
connectionResult?: IConnectionResult): Thenable<IConnectionProfile> {
this._dialogDeferredPromise = new Deferred<IConnectionProfile>();
this.showDialog(connectionManagementService,
params,
model,
connectionResult).then(() => {
}, error => {
this._dialogDeferredPromise.reject(error);
});
return this._dialogDeferredPromise;
}
public showDialog(
connectionManagementService: IConnectionManagementService,
params: INewConnectionParams,
params?: INewConnectionParams,
model?: IConnectionProfile,
connectionResult?: IConnectionResult): Thenable<void> {
@@ -297,11 +324,12 @@ export class ConnectionDialogService implements IConnectionDialogService {
});
}
private doShowDialog(params: INewConnectionParams): TPromise<void> {
if (!this._connectionDialog) {
let container = document.getElementById(this._partService.getWorkbenchElementId()).parentElement;
this._container = container;
this._connectionDialog = this._instantiationService.createInstance(ConnectionDialogWidget, this._providerTypes, this._providerNameToDisplayNameMap[this._model.providerName]);
this._connectionDialog = this._instantiationService.createInstance(ConnectionDialogWidget, this._providerTypes, this._providerNameToDisplayNameMap[this._model.providerName], this._providerNameToDisplayNameMap);
this._connectionDialog.onCancel(() => {
this._connectionDialog.databaseDropdownExpanded = this.uiController.databaseDropdownExpanded;
this.handleOnCancel(this._connectionDialog.newConnectionParams);
@@ -316,7 +344,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
this._connectionDialog.newConnectionParams = params;
return new TPromise<void>(() => {
this._connectionDialog.open(this._connectionManagementService.getRecentConnections().length > 0);
this._connectionDialog.open(this._connectionManagementService.getRecentConnections(params.providers).length > 0);
this.uiController.focusOnOpen();
});
}

View File

@@ -59,6 +59,7 @@ export class ConnectionDialogWidget extends Modal {
private $connectionUIContainer: Builder;
private _databaseDropdownExpanded: boolean;
private _actionbar: ActionBar;
private _providers: string[];
private _panel: TabbedPanel;
private _recentConnectionTabId: PanelTabIdentifier;
@@ -84,6 +85,7 @@ export class ConnectionDialogWidget extends Modal {
constructor(
private providerTypeOptions: string[],
private selectedProviderType: string,
private providerNameToDisplayNameMap: { [providerDisplayName: string]: string },
@IInstantiationService private _instantiationService: IInstantiationService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@@ -96,6 +98,22 @@ export class ConnectionDialogWidget extends Modal {
super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, contextKeyService, { hasSpinner: true, hasErrors: true });
}
public refresh(): void {
let filteredProviderTypes = this.providerTypeOptions;
if (this._newConnectionParams && this._newConnectionParams.providers) {
let validProviderNames = Object.keys(this.providerNameToDisplayNameMap).filter(x => this.includeProvider(x, this._newConnectionParams));
if (validProviderNames && validProviderNames.length > 0) {
filteredProviderTypes = filteredProviderTypes.filter(x => validProviderNames.find( v => this.providerNameToDisplayNameMap[v] === x) !== undefined);
}
}
this._providerTypeSelectBox.setOptions(filteredProviderTypes);
}
private includeProvider(providerName: string, params?: INewConnectionParams): Boolean {
return params === undefined || params.providers === undefined || params.providers.find(x => x === providerName) !== undefined;
}
protected renderBody(container: HTMLElement): void {
let connectionContainer = $('.connection-dialog');
container.appendChild(connectionContainer.getHTMLElement());
@@ -147,7 +165,7 @@ export class ConnectionDialogWidget extends Modal {
this._panel.onTabChange(c => {
if (c === savedConnectionTabId && this._savedConnectionTree.getContentHeight() === 0) {
// Update saved connection tree
TreeUpdateUtils.structuralTreeUpdate(this._savedConnectionTree, 'saved', this._connectionManagementService);
TreeUpdateUtils.structuralTreeUpdate(this._savedConnectionTree, 'saved', this._connectionManagementService, this._providers);
if (this._savedConnectionTree.getContentHeight() > 0) {
this._noSavedConnectionBuilder.hide();
@@ -366,7 +384,7 @@ export class ConnectionDialogWidget extends Modal {
this._recentConnectionBuilder.hide();
this._noRecentConnectionBuilder.show();
}
TreeUpdateUtils.structuralTreeUpdate(this._recentConnectionTree, 'recent', this._connectionManagementService);
TreeUpdateUtils.structuralTreeUpdate(this._recentConnectionTree, 'recent', this._connectionManagementService, this._providers);
// reset saved connection tree
this._savedConnectionTree.setInput([]);
@@ -415,6 +433,8 @@ export class ConnectionDialogWidget extends Modal {
public set newConnectionParams(params: INewConnectionParams) {
this._newConnectionParams = params;
this._providers = params && params.providers;
this.refresh();
}
public updateProvider(displayName: string) {

View File

@@ -22,7 +22,7 @@ export class TreeUpdateUtils {
/**
* Set input for the tree.
*/
public static structuralTreeUpdate(tree: ITree, viewKey: string, connectionManagementService: IConnectionManagementService): void {
public static structuralTreeUpdate(tree: ITree, viewKey: string, connectionManagementService: IConnectionManagementService, providers?: string[]): void {
let selectedElement: any;
let targetsToExpand: any[];
if (tree) {
@@ -35,13 +35,13 @@ export class TreeUpdateUtils {
let groups;
let treeInput = new ConnectionProfileGroup('root', null, undefined, undefined, undefined);
if (viewKey === 'recent') {
groups = connectionManagementService.getRecentConnections();
groups = connectionManagementService.getRecentConnections(providers);
treeInput.addConnections(groups);
} else if (viewKey === 'active') {
groups = connectionManagementService.getActiveConnections();
groups = connectionManagementService.getActiveConnections(providers);
treeInput.addConnections(groups);
} else if (viewKey === 'saved') {
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService, providers);
}
tree.setInput(treeInput).done(() => {
@@ -96,9 +96,9 @@ export class TreeUpdateUtils {
}
}
public static getTreeInput(connectionManagementService: IConnectionManagementService): ConnectionProfileGroup {
public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup {
let groups = connectionManagementService.getConnectionGroups();
let groups = connectionManagementService.getConnectionGroups(providers);
if (groups && groups.length > 0) {
let treeInput = groups[0];
treeInput.name = 'root';

View File

@@ -1106,7 +1106,7 @@ declare module 'sqlops' {
/**
* Connection information
*/
connection: connection.Connection;
connection?: connection.Connection;
/**
* Operation Display Name
@@ -1153,5 +1153,12 @@ declare module 'sqlops' {
* @param connectionId The ID of the connection
*/
export function getUriForConnection(connectionId: string): Thenable<string>;
/**
* Opens the connection dialog, calls the callback with the result. If connection was successful
* returns the connection otherwise returns undefined
* @param callback
*/
export function openConnectionDialog(provider?: string[]): Thenable<connection.Connection>;
}
}

View File

@@ -6,6 +6,7 @@
import { ExtHostConnectionManagementShape, SqlMainContext, MainThreadConnectionManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import { generateUuid } from 'vs/base/common/uuid';
import * as sqlops from 'sqlops';
export class ExtHostConnectionManagement extends ExtHostConnectionManagementShape {
@@ -31,6 +32,10 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
return this._proxy.$getCredentials(connectionId);
}
public $openConnectionDialog(providers?: string[]): Thenable<sqlops.connection.Connection> {
return this._proxy.$openConnectionDialog(providers);
}
public $listDatabases(connectionId: string): Thenable<string[]> {
return this._proxy.$listDatabases(connectionId);
}

View File

@@ -8,7 +8,7 @@ import { SqlExtHostContext, SqlMainContext, ExtHostConnectionManagementShape, Ma
import * as sqlops from 'sqlops';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
@@ -25,7 +25,8 @@ export class MainThreadConnectionManagement implements MainThreadConnectionManag
extHostContext: IExtHostContext,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService,
@IConnectionDialogService private _connectionDialogService: IConnectionDialogService,
) {
if (extHostContext) {
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostConnectionManagement);
@@ -49,6 +50,16 @@ export class MainThreadConnectionManagement implements MainThreadConnectionManag
return Promise.resolve(this._connectionManagementService.getActiveConnectionCredentials(connectionId));
}
public async $openConnectionDialog(providers: string[]): Promise<sqlops.connection.Connection> {
let connectionProfile = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: providers });
return connectionProfile ? {
connectionId: connectionProfile.id,
options: connectionProfile.options,
providerName: connectionProfile.providerName
} : undefined;
}
public async $listDatabases(connectionId: string): Promise<string[]> {
let connection = this._connectionManagementService.getActiveConnections().find(profile => profile.id === connectionId);
let connectionUri = this._connectionManagementService.getConnectionUri(connection);

View File

@@ -104,6 +104,9 @@ export function createApiFactory(
getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
return extHostConnectionManagement.$getCredentials(connectionId);
},
openConnectionDialog(providers?: string[]): Thenable<sqlops.connection.Connection> {
return extHostConnectionManagement.$openConnectionDialog(providers);
},
listDatabases(connectionId: string): Thenable<string[]> {
return extHostConnectionManagement.$listDatabases(connectionId);
},

View File

@@ -33,7 +33,9 @@ export abstract class ExtHostAccountManagementShape {
$refresh(handle: number, account: sqlops.Account): Thenable<sqlops.Account> { throw ni(); }
}
export abstract class ExtHostConnectionManagementShape { }
export abstract class ExtHostConnectionManagementShape {
$onConnectionOpened(handleId: string, connection: sqlops.connection.Connection): void { throw ni; }
}
export abstract class ExtHostDataProtocolShape {
@@ -491,6 +493,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
$getActiveConnections(): Thenable<sqlops.connection.Connection[]>;
$getCurrentConnection(): Thenable<sqlops.connection.Connection>;
$getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
$openConnectionDialog(providers: string[]): Thenable<sqlops.connection.Connection>;
$listDatabases(connectionId: string): Thenable<string[]>;
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
$getUriForConnection(connectionId: string): Thenable<string>;