connection contribution point (#880)

* init

* finished compile erros

* fixed all merge conflicts

* fix dialog problems

* formatting

* fix opening dialog on first open

* fix various problems with connectiondialog

* formatting

* fix tests

* added connection contrib

* formatting

* formatting and adding capabilities to shutdown

* fix connection buffering

* formatting

* fix tests
This commit is contained in:
Anthony Dresser
2018-03-28 10:58:47 -07:00
committed by GitHub
parent a14c0351ba
commit 22c54a9917
23 changed files with 1210 additions and 660 deletions

View File

@@ -239,14 +239,6 @@ export interface IConnectionManagementService {
*/
ensureDefaultLanguageFlavor(uri: string): void;
/**
* Gets an array of all known providers.
*
* @returns {string[]} An array of provider names
* @memberof IConnectionManagementService
*/
getProviderNames(): string[];
/**
* Refresh the IntelliSense cache for the connection with the given URI
*/

View File

@@ -30,12 +30,16 @@ import { warn } from 'sql/base/common/log';
import { IResourceProviderService } from 'sql/parts/accountManagement/common/interfaces';
import { IAngularEventingService, AngularEventType } from 'sql/services/angularEventing/angularEventingService';
import * as QueryConstants from 'sql/parts/query/common/constants';
import { Deferred } from 'sql/base/common/promise';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { values } from 'sql/base/common/objects';
import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionProviderExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as platform from 'vs/platform/registry/common/platform';
@@ -55,30 +59,26 @@ import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Deferred } from 'sql/base/common/promise';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
export class ConnectionManagementService implements IConnectionManagementService {
export class ConnectionManagementService extends Disposable implements IConnectionManagementService {
_serviceBrand: any;
private disposables: IDisposable[] = [];
private _providers: { [handle: string]: sqlops.ConnectionProvider; } = Object.create(null);
private _providers = new Map<string, { onReady: Thenable<sqlops.ConnectionProvider>, properties: ConnectionProviderProperties }>();
private _uriToProvider: { [uri: string]: string; } = Object.create(null);
private _connectionStatusManager: ConnectionStatusManager;
private _connectionStatusManager = new ConnectionStatusManager(this._capabilitiesService);
private _onAddConnectionProfile: Emitter<IConnectionProfile>;
private _onDeleteConnectionProfile: Emitter<void>;
private _onConnect: Emitter<IConnectionParams>;
private _onDisconnect: Emitter<IConnectionParams>;
private _onConnectRequestSent: Emitter<void>;
private _onConnectionChanged: Emitter<IConnectionParams>;
private _onLanguageFlavorChanged: Emitter<sqlops.DidChangeLanguageFlavorParams>;
private _onAddConnectionProfile = new Emitter<IConnectionProfile>();
private _onDeleteConnectionProfile = new Emitter<void>();
private _onConnect = new Emitter<IConnectionParams>();
private _onDisconnect = new Emitter<IConnectionParams>();
private _onConnectRequestSent = new Emitter<void>();
private _onConnectionChanged = new Emitter<IConnectionParams>();
private _onLanguageFlavorChanged = new Emitter<sqlops.DidChangeLanguageFlavorParams>();
private _connectionGlobalStatus: ConnectionGlobalStatus;
private _connectionGlobalStatus = new ConnectionGlobalStatus(this._statusBarService);
private _configurationEditService: ConfigurationEditingService;
@@ -103,6 +103,7 @@ export class ConnectionManagementService implements IConnectionManagementService
@IViewletService private _viewletService: IViewletService,
@IAngularEventingService private _angularEventing: IAngularEventingService
) {
super();
if (this._instantiationService) {
this._configurationEditService = this._instantiationService.createInstance(ConfigurationEditingService);
}
@@ -116,20 +117,6 @@ export class ConnectionManagementService implements IConnectionManagementService
this._configurationEditService, this._workspaceConfigurationService, this._credentialsService, this._capabilitiesService);
}
this._connectionStatusManager = new ConnectionStatusManager(this._capabilitiesService);
this._connectionGlobalStatus = new ConnectionGlobalStatus(this._statusBarService);
// Setting up our event emitters
this._onAddConnectionProfile = new Emitter<IConnectionProfile>();
this._onDeleteConnectionProfile = new Emitter<void>();
this._onConnect = new Emitter<IConnectionParams>();
this._onDisconnect = new Emitter<IConnectionParams>();
this._onConnectionChanged = new Emitter<IConnectionParams>();
this._onConnectRequestSent = new Emitter<void>();
this._onLanguageFlavorChanged = new Emitter<sqlops.DidChangeLanguageFlavorParams>();
this._onProvidersReady = new Deferred();
// Register Statusbar item
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
ConnectionStatusbarItem,
@@ -137,19 +124,33 @@ export class ConnectionManagementService implements IConnectionManagementService
100 /* High Priority */
));
if (_capabilitiesService) {
_capabilitiesService.onCapabilitiesRegistered((p => {
if (p === 'MSSQL') {
if (!this.hasRegisteredServers()) {
// prompt the user for a new connection on startup if no profiles are registered
this.showConnectionDialog();
}
}
}));
if (_capabilitiesService && Object.keys(_capabilitiesService.providers).length > 0 && !this.hasRegisteredServers()) {
// prompt the user for a new connection on startup if no profiles are registered
this.showConnectionDialog();
} else if (_capabilitiesService && !this.hasRegisteredServers()) {
_capabilitiesService.onCapabilitiesRegistered(e => {
// prompt the user for a new connection on startup if no profiles are registered
this.showConnectionDialog();
});
}
this.disposables.push(this._onAddConnectionProfile);
this.disposables.push(this._onDeleteConnectionProfile);
const registry = platform.Registry.as<IConnectionProviderRegistry>(ConnectionProviderExtensions.ConnectionProviderContributions);
let providerRegistration = (p: { id: string, properties: ConnectionProviderProperties }) => {
let provider = {
onReady: new Deferred<sqlops.ConnectionProvider>(),
properties: p.properties
};
this._providers.set(p.id, provider);
};
registry.onNewProvider(providerRegistration, this);
Object.entries(registry.providers).map(v => {
providerRegistration({ id: v[0], properties: v[1] });
});
this._register(this._onAddConnectionProfile);
this._register(this._onDeleteConnectionProfile);
// Refresh editor titles when connections start/end/change to ensure tabs are colored correctly
this.onConnectionChanged(() => this.refreshEditorTitles());
@@ -186,37 +187,21 @@ export class ConnectionManagementService implements IConnectionManagementService
return this._onLanguageFlavorChanged.event;
}
private _onProvidersReady: Deferred<void>;
private onProvidersReady(): Promise<void> {
return this._onProvidersReady.promise;
}
private _providerCount: number = 0;
// Connection Provider Registration
public registerProvider(providerId: string, provider: sqlops.ConnectionProvider): void {
this._providers[providerId] = provider;
// temporarily close splash screen when a connection provider has been registered
// @todo remove this code once a proper initialization event is available (karlb 4/1/2017)
++this._providerCount;
this._onProvidersReady.resolve();
if (this._providerCount === 1) {
// show the Registered Server viewlet
let startupConfig = this._workspaceConfigurationService.getValue('startup');
if (startupConfig) {
let showServerViewlet = <boolean>startupConfig['alwaysShowServersView'];
if (showServerViewlet) {
// only show the Servers viewlet if there isn't another active viewlet
if (!this._viewletService.getActiveViewlet()) {
this._commandService.executeCommand('workbench.view.connections', {});
}
}
}
if (!this._providers.has(providerId)) {
console.error('Provider', providerId, 'attempted to register but has no metadata');
let providerType = {
onReady: new Deferred<sqlops.ConnectionProvider>(),
properties: undefined
};
this._providers.set(providerId, providerType);
}
// we know this is a deferred promise because we made it
(this._providers.get(providerId).onReady as Deferred<sqlops.ConnectionProvider>).resolve(provider);
}
/**
@@ -675,19 +660,15 @@ export class ConnectionManagementService implements IConnectionManagementService
});
}
public getProviderNames(): string[] {
return Object.keys(this._providers);
}
public getAdvancedProperties(): sqlops.ConnectionOption[] {
let providers = this._capabilitiesService.providers;
if (providers !== undefined && providers.length > 0) {
if (providers) {
// just grab the first registered provider for now, this needs to change
// to lookup based on currently select provider
let providerCapabilities = this._capabilitiesService.getCapabilities(providers[0]);
if (!!providerCapabilities.connectionProvider) {
return providerCapabilities.connectionProvider.options;
let providerCapabilities = values(providers)[0];
if (!!providerCapabilities.connection) {
return providerCapabilities.connection.connectionOptions;
}
}
@@ -752,7 +733,7 @@ export class ConnectionManagementService implements IConnectionManagementService
* @memberof ConnectionManagementService
*/
public doChangeLanguageFlavor(uri: string, language: string, provider: string): void {
if (provider in this._providers) {
if (this._providers.has(provider)) {
this._onLanguageFlavorChanged.fire({
uri: uri,
language: language,
@@ -772,7 +753,7 @@ export class ConnectionManagementService implements IConnectionManagementService
if (!this.getProviderIdFromUri(uri)) {
// Lookup the default settings and use this
let defaultProvider = WorkbenchUtils.getSqlConfigValue<string>(this._workspaceConfigurationService, Constants.defaultEngine);
if (defaultProvider && defaultProvider in this._providers) {
if (defaultProvider && this._providers.has(defaultProvider)) {
// Only set a default if it's in the list of registered providers
this.doChangeLanguageFlavor(uri, 'sql', defaultProvider);
}
@@ -788,15 +769,13 @@ export class ConnectionManagementService implements IConnectionManagementService
// setup URI to provider ID map for connection
this._uriToProvider[uri] = connection.providerName;
return new Promise<boolean>((resolve, reject) => {
this.onProvidersReady().then(() => {
this._providers[connection.providerName].connect(uri, connectionInfo);
this._onConnectRequestSent.fire();
return this._providers.get(connection.providerName).onReady.then((provider) => {
provider.connect(uri, connectionInfo);
this._onConnectRequestSent.fire();
// TODO make this generic enough to handle non-SQL languages too
this.doChangeLanguageFlavor(uri, 'sql', connection.providerName);
resolve(true);
});
// TODO make this generic enough to handle non-SQL languages too
this.doChangeLanguageFlavor(uri, 'sql', connection.providerName);
return true;
});
}
@@ -806,9 +785,9 @@ export class ConnectionManagementService implements IConnectionManagementService
return Promise.resolve(false);
}
return new Promise<boolean>((resolve, reject) => {
this._providers[providerId].disconnect(uri);
resolve(true);
return this._providers.get(providerId).onReady.then(provider => {
provider.disconnect(uri);
return true;
});
}
@@ -818,9 +797,9 @@ export class ConnectionManagementService implements IConnectionManagementService
return Promise.resolve(false);
}
return new Promise<boolean>((resolve, reject) => {
this._providers[providerId].cancelConnect(uri);
resolve(true);
return this._providers.get(providerId).onReady.then(provider => {
provider.cancelConnect(uri);
return true;
});
}
@@ -830,15 +809,12 @@ export class ConnectionManagementService implements IConnectionManagementService
return Promise.resolve(undefined);
}
return new Promise<sqlops.ListDatabasesResult>((resolve, reject) => {
let provider = this._providers[providerId];
provider.listDatabases(uri).then(result => {
return this._providers.get(providerId).onReady.then(provider => {
return provider.listDatabases(uri).then(result => {
if (result && result.databaseNames) {
result.databaseNames.sort();
}
resolve(result);
}, error => {
reject(error);
return result;
});
});
}
@@ -932,10 +908,6 @@ export class ConnectionManagementService implements IConnectionManagementService
public onIntelliSenseCacheComplete(handle: number, connectionUri: string): void {
}
public dispose(): void {
this.disposables = dispose(this.disposables);
}
public shutdown(): void {
this._connectionStore.clearActiveConnections();
this._connectionMemento.saveMemento();
@@ -1202,12 +1174,13 @@ export class ConnectionManagementService implements IConnectionManagementService
return Promise.resolve(false);
}
let provider = this._providers[providerId];
return provider.changeDatabase(connectionUri, databaseName).then(result => {
if (result) {
this.getConnectionProfile(connectionUri).databaseName = databaseName;
}
return result;
return this._providers.get(providerId).onReady.then(provider => {
return provider.changeDatabase(connectionUri, databaseName).then(result => {
if (result) {
this.getConnectionProfile(connectionUri).databaseName = databaseName;
}
return result;
});
});
}
return Promise.resolve(false);
@@ -1316,8 +1289,7 @@ export class ConnectionManagementService implements IConnectionManagementService
return Promise.reject('No provider corresponding to the given URI');
}
let provider = this._providers[providerId];
return provider.rebuildIntelliSenseCache(connectionUri);
return this._providers.get(providerId).onReady.then(provider => provider.rebuildIntelliSenseCache(connectionUri));
}
return Promise.reject('The given URI is not currently connected');
}
@@ -1354,7 +1326,7 @@ export class ConnectionManagementService implements IConnectionManagementService
}
// Find the password option for the connection provider
let passwordOption = this._capabilitiesService.getCapabilities(profile.providerName).connectionProvider.options.find(
let passwordOption = this._capabilitiesService.getCapabilities(profile.providerName).connection.connectionOptions.find(
option => option.specialValueType === ConnectionOptionSpecialType.password);
if (!passwordOption) {
return undefined;

View File

@@ -13,13 +13,14 @@ import * as interfaces from 'sql/parts/connection/common/interfaces';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as Constants from 'sql/parts/connection/common/constants';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
export class ProviderConnectionInfo extends Disposable implements sqlops.ConnectionInfo {
options: { [name: string]: any } = {};
private _providerName: string;
protected _serverCapabilities: sqlops.DataProtocolServerCapabilities;
protected _serverCapabilities: ConnectionProviderProperties;
private static readonly SqlAuthentication = 'SqlLogin';
public static readonly ProviderPropertyName = 'providerName';
@@ -34,7 +35,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
if (!isString(model)) {
if (model.options && this._serverCapabilities) {
this._serverCapabilities.connectionProvider.options.forEach(option => {
this._serverCapabilities.connectionOptions.forEach(option => {
let value = model.options[option.name];
this.options[option.name] = value;
});
@@ -55,10 +56,13 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
public set providerName(name: string) {
this._providerName = name;
if (!this._serverCapabilities) {
this._serverCapabilities = this.capabilitiesService.getCapabilities(this.providerName);
let capabilities = this.capabilitiesService.getCapabilities(this.providerName);
if (capabilities) {
this._serverCapabilities = capabilities.connection;
}
this._register(this.capabilitiesService.onCapabilitiesRegistered(e => {
if (e === this.providerName) {
this._serverCapabilities = this.capabilitiesService.getCapabilities(e);
if (e.connection.providerId === this.providerName) {
this._serverCapabilities = e.connection;
}
}));
}
@@ -70,7 +74,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
return instance;
}
public get serverCapabilities(): sqlops.DataProtocolServerCapabilities {
public get serverCapabilities(): ConnectionProviderProperties {
return this._serverCapabilities;
}
@@ -141,7 +145,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
}
public isPasswordRequired(): boolean {
let optionMetadata = this._serverCapabilities.connectionProvider.options.find(
let optionMetadata = this._serverCapabilities.connectionOptions.find(
option => option.specialValueType === ConnectionOptionSpecialType.password);
let isPasswordRequired: boolean = optionMetadata.isRequired;
if (this.providerName === Constants.mssqlProviderName) {
@@ -166,7 +170,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
public getOptionsKey(): string {
let idNames = [];
if (this._serverCapabilities) {
idNames = this._serverCapabilities.connectionProvider.options.map(o => {
idNames = this._serverCapabilities.connectionOptions.map(o => {
if ((o.specialValueType || o.isIdentity) && o.specialValueType !== ConnectionOptionSpecialType.password) {
return o.name;
} else {
@@ -210,7 +214,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
public getSpecialTypeOptionName(type: string): string {
if (this._serverCapabilities) {
let optionMetadata = this._serverCapabilities.connectionProvider.options.find(o => o.specialValueType === type);
let optionMetadata = this._serverCapabilities.connectionOptions.find(o => o.specialValueType === type);
return !!optionMetadata ? optionMetadata.name : undefined;
} else {
return type.toString();
@@ -225,7 +229,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
}
public get authenticationTypeDisplayName(): string {
let optionMetadata = this._serverCapabilities.connectionProvider.options.find(o => o.specialValueType === ConnectionOptionSpecialType.authType);
let optionMetadata = this._serverCapabilities.connectionOptions.find(o => o.specialValueType === ConnectionOptionSpecialType.authType);
let authType = this.authenticationType;
let displayName: string = authType;
@@ -240,7 +244,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
}
public getProviderOptions(): sqlops.ConnectionOption[] {
return this._serverCapabilities.connectionProvider.options;
return this._serverCapabilities.connectionOptions;
}
public static get idSeparator(): string {
@@ -258,7 +262,7 @@ export class ProviderConnectionInfo extends Disposable implements sqlops.Connect
parts.push(this.databaseName);
parts.push(this.authenticationTypeDisplayName);
this._serverCapabilities.connectionProvider.options.forEach(element => {
this._serverCapabilities.connectionOptions.forEach(element => {
if (element.specialValueType !== ConnectionOptionSpecialType.serverName &&
element.specialValueType !== ConnectionOptionSpecialType.databaseName &&
element.specialValueType !== ConnectionOptionSpecialType.authType &&

View File

@@ -16,6 +16,7 @@ import * as sqlops from 'sqlops';
import * as Utils from 'sql/parts/connection/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
export class ConnectionController implements IConnectionComponentController {
private _container: HTMLElement;
@@ -31,14 +32,14 @@ export class ConnectionController implements IConnectionComponentController {
constructor(container: HTMLElement,
connectionManagementService: IConnectionManagementService,
sqlCapabilities: sqlops.DataProtocolServerCapabilities,
connectionProperties: ConnectionProviderProperties,
callback: IConnectionComponentCallbacks,
providerName: string,
@IInstantiationService private _instantiationService: IInstantiationService ) {
@IInstantiationService private _instantiationService: IInstantiationService) {
this._container = container;
this._connectionManagementService = connectionManagementService;
this._callback = callback;
this._providerOptions = sqlCapabilities.connectionProvider.options;
this._providerOptions = connectionProperties.connectionOptions;
var specialOptions = this._providerOptions.filter(
(property) => (property.specialValueType !== null && property.specialValueType !== undefined));
this._connectionWidget = this._instantiationService.createInstance(ConnectionWidget, specialOptions, {
@@ -48,8 +49,8 @@ export class ConnectionController implements IConnectionComponentController {
onSetAzureTimeOut: () => this.handleonSetAzureTimeOut(),
onFetchDatabases: (serverName: string, authenticationType: string, userName?: string, password?: string) => this.onFetchDatabases(
serverName, authenticationType, userName, password).then(result => {
return result;
})
return result;
})
}, providerName);
this._providerName = providerName;
}
@@ -65,7 +66,7 @@ export class ConnectionController implements IConnectionComponentController {
let uri = this._connectionManagementService.getConnectionId(tempProfile);
return new Promise<string[]>((resolve, reject) => {
if (this._databaseCache.has(uri)) {
let cachedDatabases : string[] = this._databaseCache.get(uri);
let cachedDatabases: string[] = this._databaseCache.get(uri);
if (cachedDatabases !== null) {
resolve(cachedDatabases);
} else {

View File

@@ -32,6 +32,7 @@ import { IWindowsService } from 'vs/platform/windows/common/windows';
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 { entries } from 'sql/base/common/objects';
export interface IConnectionValidateResult {
isValid: boolean;
@@ -190,7 +191,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
// Set the model name, initialize the controller if needed, and return the controller
this._model.providerName = providerName;
if (!this._connectionControllerMap[providerName]) {
this._connectionControllerMap[providerName] = this._instantiationService.createInstance(ConnectionController, this._container, this._connectionManagementService, this._capabilitiesService.getCapabilities(providerName), {
this._connectionControllerMap[providerName] = this._instantiationService.createInstance(ConnectionController, this._container, this._connectionManagementService, this._capabilitiesService.getCapabilities(providerName).connection, {
onSetConnectButton: (enable: boolean) => this.handleSetConnectButtonEnable(enable)
}, providerName);
}
@@ -274,12 +275,9 @@ export class ConnectionDialogService implements IConnectionDialogService {
// only create the provider maps first time the dialog gets called
let capabilitiesPromise: Promise<void> = Promise.resolve();
if (this._providerTypes.length === 0) {
capabilitiesPromise = this._capabilitiesService.onCapabilitiesReady().then(() => {
this._capabilitiesService.providers.map(p => {
let capabilities = this._capabilitiesService.getCapabilities(p);
this._providerTypes.push(capabilities.providerDisplayName);
this._providerNameToDisplayNameMap[capabilities.providerName] = capabilities.providerDisplayName;
});
entries(this._capabilitiesService.providers).forEach(p => {
this._providerTypes.push(p[1].connection.displayName);
this._providerNameToDisplayNameMap[p[0]] = p[1].connection.displayName;
});
}
capabilitiesPromise.then(s => {