Introduce connection API (#598)

Including getCurrentConnection, getActiveConnections, and getCredentials
This commit is contained in:
Matt Irvine
2018-02-06 15:56:49 -08:00
committed by GitHub
parent f5aa49ebb9
commit 3df522536f
15 changed files with 264 additions and 3 deletions

37
src/sql/data.d.ts vendored
View File

@@ -71,6 +71,43 @@ declare module 'data' {
export function registerProvider(provider: SerializationProvider): vscode.Disposable;
}
/**
* Namespace for connection management
*/
export namespace connection {
/**
* Get the current connection based on the active editor or Object Explorer selection
*/
export function getCurrentConnection(): Thenable<Connection>;
/**
* Get all active connections
*/
export function getActiveConnections(): Thenable<Connection[]>;
/**
* Get the credentials for an active connection
* @param {string} connectionId The id of the connection
* @returns {{ [name: string]: string}} A dictionary containing the credentials as they would be included in the connection's options dictionary
*/
export function getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
/**
* Interface for representing a connection when working with connection APIs
*/
export interface Connection extends ConnectionInfo {
/**
* The name of the provider managing the connection (e.g. MSSQL)
*/
providerName: string;
/**
* A unique identifier for the connection
*/
connectionId: string;
}
}
// EXPORTED INTERFACES /////////////////////////////////////////////////
export interface ConnectionInfo {

View File

@@ -253,6 +253,21 @@ export interface IConnectionManagementService {
* Refresh the IntelliSense cache for the connection with the given URI
*/
rebuildIntelliSenseCache(uri: string): Thenable<void>;
/**
* Get a copy of the connection profile with its passwords removed
* @param {IConnectionProfile} profile The connection profile to remove passwords from
* @returns {IConnectionProfile} A copy of the connection profile with passwords removed
*/
removeConnectionProfileCredentials(profile: IConnectionProfile): IConnectionProfile;
/**
* Get the credentials for a connected connection profile, as they would appear in the options dictionary
* @param {string} profileId The id of the connection profile to get the password for
* @returns {{ [name: string]: string }} A dictionary containing the credentials as they would be included
* in the connection profile's options dictionary, or undefined if the profile is not connected
*/
getActiveConnectionCredentials(profileId: string): { [name: string]: string };
}
export const IConnectionDialogService = createDecorator<IConnectionDialogService>('connectionDialogService');

View File

@@ -56,6 +56,7 @@ 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 {
@@ -659,7 +660,7 @@ export class ConnectionManagementService implements IConnectionManagementService
}
public getActiveConnections(): ConnectionProfile[] {
return this._connectionStore.getActiveConnections();
return this._connectionStatusManager.getActiveConnectionProfiles();
}
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
@@ -1351,4 +1352,26 @@ export class ConnectionManagementService implements IConnectionManagementService
this._editorGroupService.refreshEditorTitles();
}
}
public removeConnectionProfileCredentials(originalProfile: IConnectionProfile): IConnectionProfile {
return this._connectionStore.getProfileWithoutPassword(originalProfile);
}
public getActiveConnectionCredentials(profileId: string): { [name: string]: string } {
let profile = this.getActiveConnections().find(connectionProfile => connectionProfile.id === profileId);
if (!profile) {
return undefined;
}
// Find the password option for the connection provider
let passwordOption = this._capabilitiesService.getCapabilities().find(capability => capability.providerName === profile.providerName).connectionProvider.options.find(
option => option.specialValueType === ConnectionOptionSpecialType.password);
if (!passwordOption) {
return undefined;
}
let credentials = {};
credentials[passwordOption.name] = profile.options[passwordOption.name];
return credentials;
}
}

View File

@@ -209,4 +209,13 @@ export class ConnectionStatusManager {
}
return providerId;
}
/**
* Get a list of the active connection profiles managed by the status manager
*/
public getActiveConnectionProfiles(): 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);
}
}

View File

@@ -19,6 +19,7 @@ import { ConfigurationEditingService } from 'vs/workbench/services/configuration
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import * as data from 'data';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
const MAX_CONNECTIONS_DEFAULT = 25;

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { ExtHostConnectionManagementShape, SqlMainContext, MainThreadConnectionManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import * as data from 'data';
export class ExtHostConnectionManagement extends ExtHostConnectionManagementShape {
private _proxy: MainThreadConnectionManagementShape;
constructor(
threadService: IThreadService
) {
super();
this._proxy = threadService.get(SqlMainContext.MainThreadConnectionManagement);
}
public $getActiveConnections(): Thenable<data.connection.Connection[]> {
return this._proxy.$getActiveConnections();
}
public $getCurrentConnection(): Thenable<data.connection.Connection> {
return this._proxy.$getCurrentConnection();
}
public $getCredentials(connectionId: string): Thenable<{ [name: string]: string}> {
return this._proxy.$getCredentials(connectionId);
}
}

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { SqlExtHostContext, SqlMainContext, ExtHostConnectionManagementShape, MainThreadConnectionManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import * as data from 'data';
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 { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
@extHostNamedCustomer(SqlMainContext.MainThreadConnectionManagement)
export class MainThreadConnectionManagement implements MainThreadConnectionManagementShape {
private _proxy: ExtHostConnectionManagementShape;
private _toDispose: IDisposable[];
constructor(
extHostContext: IExtHostContext,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService
) {
if (extHostContext) {
this._proxy = extHostContext.get(SqlExtHostContext.ExtHostConnectionManagement);
}
this._toDispose = [];
}
public dispose(): void {
this._toDispose = dispose(this._toDispose);
}
public $getActiveConnections(): Thenable<data.connection.Connection[]> {
return Promise.resolve(this._connectionManagementService.getActiveConnections().map(profile => this.convertConnection(profile)));
}
public $getCurrentConnection(): Thenable<data.connection.Connection> {
return Promise.resolve(this.convertConnection(TaskUtilities.getCurrentGlobalConnection(this._objectExplorerService, this._connectionManagementService, this._workbenchEditorService, true)));
}
public $getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
return Promise.resolve(this._connectionManagementService.getActiveConnectionCredentials(connectionId));
}
private convertConnection(profile: IConnectionProfile): data.connection.Connection {
if (!profile) {
return undefined;
}
profile = this._connectionManagementService.removeConnectionProfileCredentials(profile);
let connection: data.connection.Connection = {
providerName: profile.providerName,
connectionId: profile.id,
options: profile.options
};
return connection;
}
}

View File

@@ -28,6 +28,7 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration
import { ExtHostModalDialogs } from 'sql/workbench/api/node/extHostModalDialog';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl';
import { ExtHostConnectionManagement } from 'sql/workbench/api/node/extHostConnectionManagement';
export interface ISqlExtensionApiFactory {
vsCodeFactory(extension: IExtensionDescription): typeof vscode;
@@ -49,6 +50,7 @@ export function createApiFactory(
// Addressable instances
const extHostAccountManagement = threadService.set(SqlExtHostContext.ExtHostAccountManagement, new ExtHostAccountManagement(threadService));
const extHostConnectionManagement = threadService.set(SqlExtHostContext.ExtHostConnectionManagement, new ExtHostConnectionManagement(threadService));
const extHostCredentialManagement = threadService.set(SqlExtHostContext.ExtHostCredentialManagement, new ExtHostCredentialManagement(threadService));
const extHostDataProvider = threadService.set(SqlExtHostContext.ExtHostDataProtocol, new ExtHostDataProtocol(threadService));
const extHostSerializationProvider = threadService.set(SqlExtHostContext.ExtHostSerializationProvider, new ExtHostSerializationProvider(threadService));
@@ -74,6 +76,19 @@ export function createApiFactory(
}
};
// namespace: connection
const connection: typeof data.connection = {
getActiveConnections(): Thenable<data.connection.Connection[]> {
return extHostConnectionManagement.$getActiveConnections();
},
getCurrentConnection(): Thenable<data.connection.Connection> {
return extHostConnectionManagement.$getCurrentConnection();
},
getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
return extHostConnectionManagement.$getCredentials(connectionId);
}
};
// namespace: credentials
const credentials: typeof data.credentials = {
registerProvider(provider: data.CredentialProvider): vscode.Disposable {
@@ -246,6 +261,7 @@ export function createApiFactory(
return {
accounts,
connection,
credentials,
resources,
serialization,

View File

@@ -10,6 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
// --- SQL contributions
import 'sql/workbench/api/node/mainThreadConnectionManagement';
import 'sql/workbench/api/node/mainThreadCredentialManagement';
import 'sql/workbench/api/node/mainThreadDataProtocol';
import 'sql/workbench/api/node/mainThreadSerializationProvider';

View File

@@ -23,6 +23,8 @@ export abstract class ExtHostAccountManagementShape {
$refresh(handle: number, account: data.Account): Thenable<data.Account> { throw ni(); }
}
export abstract class ExtHostConnectionManagementShape { }
export abstract class ExtHostDataProtocolShape {
/**
@@ -389,6 +391,12 @@ export interface MainThreadDataProtocolShape extends IDisposable {
$onEditSessionReady(handle: number, ownerUri: string, success: boolean, message: string);
}
export interface MainThreadConnectionManagementShape extends IDisposable {
$getActiveConnections(): Thenable<data.connection.Connection[]>;
$getCurrentConnection(): Thenable<data.connection.Connection>;
$getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
}
export interface MainThreadCredentialManagementShape extends IDisposable {
$registerCredentialProvider(handle: number): TPromise<any>;
$unregisterCredentialProvider(handle: number): TPromise<any>;
@@ -406,6 +414,7 @@ function ni() { return new Error('Not implemented'); }
export const SqlMainContext = {
// SQL entries
MainThreadAccountManagement: createMainId<MainThreadAccountManagementShape>('MainThreadAccountManagement'),
MainThreadConnectionManagement: createMainId<MainThreadConnectionManagementShape>('MainThreadConnectionManagement'),
MainThreadCredentialManagement: createMainId<MainThreadCredentialManagementShape>('MainThreadCredentialManagement'),
MainThreadDataProtocol: createMainId<MainThreadDataProtocolShape>('MainThreadDataProtocol'),
MainThreadSerializationProvider: createMainId<MainThreadSerializationProviderShape>('MainThreadSerializationProvider'),
@@ -415,6 +424,7 @@ export const SqlMainContext = {
export const SqlExtHostContext = {
ExtHostAccountManagement: createExtId<ExtHostAccountManagementShape>('ExtHostAccountManagement'),
ExtHostConnectionManagement: createExtId<ExtHostConnectionManagementShape>('ExtHostConnectionManagement'),
ExtHostCredentialManagement: createExtId<ExtHostCredentialManagementShape>('ExtHostCredentialManagement'),
ExtHostDataProtocol: createExtId<ExtHostDataProtocolShape>('ExtHostDataProtocol'),
ExtHostSerializationProvider: createExtId<ExtHostSerializationProviderShape>('ExtHostSerializationProvider'),

View File

@@ -348,15 +348,20 @@ export function openInsight(query: IInsightsConfig, profile: IConnectionProfile,
* Get the current global connection, which is the connection from the active editor, unless OE
* is focused or there is no such editor, in which case it comes from the OE selection. Returns
* undefined when there is no such connection.
*
* @param objectExplorerService
* @param connectionManagementService
* @param workbenchEditorService
* @param topLevelOnly If true, only return top-level (i.e. connected) Object Explorer connections instead of database connections when appropriate
*/
export function getCurrentGlobalConnection(objectExplorerService: IObjectExplorerService, connectionManagementService: IConnectionManagementService, workbenchEditorService: IWorkbenchEditorService): IConnectionProfile {
export function getCurrentGlobalConnection(objectExplorerService: IObjectExplorerService, connectionManagementService: IConnectionManagementService, workbenchEditorService: IWorkbenchEditorService, topLevelOnly: boolean = false): IConnectionProfile {
let connection: IConnectionProfile;
let objectExplorerSelection = objectExplorerService.getSelectedProfileAndDatabase();
if (objectExplorerSelection) {
let objectExplorerProfile = objectExplorerSelection.profile;
if (connectionManagementService.isProfileConnected(objectExplorerProfile)) {
if (objectExplorerSelection.databaseName) {
if (objectExplorerSelection.databaseName && !topLevelOnly) {
connection = objectExplorerProfile.cloneWithDatabase(objectExplorerSelection.databaseName);
} else {
connection = objectExplorerProfile;