Initial LiveShare extension scaffolding (#7170)

* LiveShare initial shared connection

* Various cleanups

* Fix type

* Fix hygiene
This commit is contained in:
Karl Burtram
2019-09-11 15:24:08 -07:00
committed by GitHub
parent 9765b0ed8e
commit 9df66deb81
33 changed files with 3126 additions and 36 deletions

7
src/sql/azdata.d.ts vendored
View File

@@ -33,7 +33,7 @@ declare module 'azdata' {
export function registerMetadataProvider(provider: MetadataProvider): vscode.Disposable;
export function registerQueryProvider(provider: QueryProvider): vscode.Disposable;
export function registerQueryProvider(provider: QueryProvider, isLiveShare?: boolean): vscode.Disposable;
export function registerAdminServicesProvider(provider: AdminServicesProvider): vscode.Disposable;
@@ -93,7 +93,7 @@ declare module 'azdata' {
azureTenantId?: string;
options: { [name: string]: any };
static createFrom(options: any[]): ConnectionProfile;
static createFrom(options: Map<string, any>): ConnectionProfile;
}
/**
@@ -3825,6 +3825,9 @@ declare module 'azdata' {
// tab content is build using the modelview UI builder APIs
// probably should rename DialogTab class since it is useful outside dialogs
createQueryTab(tab: window.DialogTab): void;
// connect the query document using the given connection profile
connect(connectionProfile: connection.ConnectionProfile): Thenable<void>;
}
/**

View File

@@ -25,6 +25,8 @@ declare module 'azdata' {
* Register a connection event listener
*/
export function registerConnectionEventListener(listener: connection.ConnectionEventListener): void;
export function getConnection(uri: string): Thenable<ConnectionProfile>;
}

View File

@@ -277,6 +277,8 @@ export interface IConnectionManagementService {
* @returns array of connections
*/
getConnections(activeConnectionsOnly?: boolean): ConnectionProfile[];
getConnection(uri: string): ConnectionProfile;
}
export enum RunQueryOnConnectionMode {

View File

@@ -156,8 +156,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
return this._onLanguageFlavorChanged.event;
}
private _providerCount: number = 0;
// Connection Provider Registration
public registerProvider(providerId: string, provider: azdata.ConnectionProvider): void {
if (!this._providers.has(providerId)) {
@@ -1337,7 +1335,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
* @returns array of connections
**/
public getConnections(activeConnectionsOnly?: boolean): ConnectionProfile[] {
// 1. Active Connections
const connections = this.getActiveConnections();
@@ -1368,6 +1365,20 @@ export class ConnectionManagementService extends Disposable implements IConnecti
return connections;
}
public getConnection(uri: string): ConnectionProfile {
const connections = this.getActiveConnections();
if (connections) {
for (let connection of connections) {
let connectionUri = this.getConnectionUriFromId(connection.id);
if (connectionUri === uri) {
return connection;
}
}
}
return undefined;
}
private getConnectionsInGroup(group: ConnectionProfileGroup): ConnectionProfile[] {
const connections = [];
if (group) {

View File

@@ -33,8 +33,7 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
public constructor(
capabilitiesService: ICapabilitiesService,
model: string | azdata.IConnectionProfile
) {
model: string | azdata.IConnectionProfile) {
super(capabilitiesService, model);
if (model && !isString(model)) {
this.groupId = model.groupId;

View File

@@ -189,7 +189,14 @@ export class ConnectionStatusManager {
return undefined;
}
private isSharedSession(fileUri: string): boolean {
return fileUri && fileUri.startsWith('vsls:');
}
public isConnected(id: string): boolean {
if (this.isSharedSession(id)) {
return true;
}
return (id in this._connections && this._connections[id].connectionId && !!this._connections[id].connectionId);
}

View File

@@ -70,7 +70,7 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect
public set providerName(name: string) {
this._providerName = name;
if (!this._serverCapabilities) {
if (!this._serverCapabilities && this.capabilitiesService) {
let capabilities = this.capabilitiesService.getCapabilities(this.providerName);
if (capabilities) {
this._serverCapabilities = capabilities.connection;
@@ -192,6 +192,11 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect
}
public isPasswordRequired(): boolean {
// if there is no provider capabilities metadata assume a password is not required
if (!this._serverCapabilities) {
return false;
}
let optionMetadata = this._serverCapabilities.connectionOptions.find(
option => option.specialValueType === ConnectionOptionSpecialType.password);
let isPasswordRequired: boolean = optionMetadata.isRequired;

View File

@@ -294,4 +294,8 @@ export class TestConnectionManagementService implements IConnectionManagementSer
getConnections(activeConnectionsOnly?: boolean): ConnectionProfile[] {
return [];
}
getConnection(uri: string): ConnectionProfile {
return undefined;
}
}

View File

@@ -82,6 +82,31 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
return Promise.resolve(this._connectionManagementService.getConnections(activeConnectionsOnly).map(profile => this.convertToConnectionProfile(profile)));
}
public $getConnection(uri: string): Thenable<azdata.connection.ConnectionProfile> {
let profile = this._connectionManagementService.getConnection(uri);
if (!profile) {
return Promise.resolve(undefined);
}
let connection: azdata.connection.ConnectionProfile = {
providerId: profile.providerName,
connectionId: profile.id,
connectionName: profile.connectionName,
serverName: profile.serverName,
databaseName: profile.databaseName,
userName: profile.userName,
password: profile.password,
authenticationType: profile.authenticationType,
savePassword: profile.savePassword,
groupFullName: profile.groupFullName,
groupId: profile.groupId,
saveProfile: profile.savePassword,
azureTenantId: profile.azureTenantId,
options: profile.options
};
return Promise.resolve(connection);
}
public $getActiveConnections(): Thenable<azdata.connection.Connection[]> {
return Promise.resolve(this._connectionManagementService.getActiveConnections().map(profile => this.convertConnection(profile)));
}

View File

@@ -13,6 +13,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import * as azdata from 'azdata';
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
@extHostNamedCustomer(SqlMainContext.MainThreadQueryEditor)
export class MainThreadQueryEditor extends Disposable implements MainThreadQueryEditorShape {
@@ -62,6 +64,36 @@ export class MainThreadQueryEditor extends Disposable implements MainThreadQuery
});
}
private static connectionProfileToIConnectionProfile(connection: azdata.connection.ConnectionProfile): IConnectionProfile {
let profile: ConnectionProfile = new ConnectionProfile(undefined, undefined);
profile.options = connection.options;
profile.providerName = connection.options['providerName'];
return profile.toIConnectionProfile();
}
public $connectWithProfile(fileUri: string, connection: azdata.connection.ConnectionProfile): Thenable<void> {
return new Promise<void>(async (resolve, reject) => {
let editors = this._editorService.visibleControls.filter(resource => {
return !!resource && resource.input.getResource().toString() === fileUri;
});
let editor = editors && editors.length > 0 ? editors[0] : undefined;
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: editor ? editor.input as any : undefined },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: false,
showFirewallRuleOnError: false,
};
let profile: IConnectionProfile = MainThreadQueryEditor.connectionProfileToIConnectionProfile(connection);
let connectionResult = await this._connectionManagementService.connect(profile, fileUri, options);
if (connectionResult && connectionResult.connected) {
console.log(`editor ${fileUri} connected`);
}
});
}
public $runQuery(fileUri: string, runCurrentQuery: boolean = true): void {
let filteredEditors = this._editorService.visibleControls.filter(editor => editor.input.getResource().toString() === fileUri);
if (filteredEditors && filteredEditors.length > 0) {

View File

@@ -20,10 +20,6 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadConnectionManagement);
}
public $getCurrentConnection(): Thenable<azdata.connection.ConnectionProfile> {
return this._proxy.$getCurrentConnectionProfile();
}
public $onConnectionEvent(handle: number, type: azdata.connection.ConnectionEventType, ownerUri: string, profile: azdata.IConnectionProfile): void {
let listener = this._connectionListeners[handle];
if (listener) {
@@ -37,10 +33,18 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
this._nextListenerHandle++;
}
public $getCurrentConnection(): Thenable<azdata.connection.ConnectionProfile> {
return this._proxy.$getCurrentConnectionProfile();
}
public $getConnections(activeConnectionsOnly?: boolean): Thenable<azdata.connection.ConnectionProfile[]> {
return this._proxy.$getConnections(activeConnectionsOnly);
}
public $getConnection(uri: string): Thenable<azdata.connection.ConnectionProfile> {
return this._proxy.$getConnection(uri);
}
// "sqlops" back-compat connection APIs
public $getActiveConnections(): Thenable<azdata.connection.Connection[]> {
return this._proxy.$getActiveConnections();

View File

@@ -251,6 +251,7 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
if (this.uriTransformer) {
ownerUri = URI.from(this.uriTransformer.transformIncoming(URI.parse(ownerUri))).toString(true);
}
return this._resolveProvider<azdata.QueryProvider>(handle).runQuery(ownerUri, selection, runOptions);
}
@@ -274,6 +275,10 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
}
}
$connectWithProfile(handle: number, ownerUri: string, profile: azdata.connection.ConnectionProfile): Thenable<void> {
return new Promise((r) => r());
}
$parseSyntax(handle: number, ownerUri: string, query: string): Thenable<azdata.SyntaxParseResult> {
return this._resolveProvider<azdata.QueryProvider>(handle).parseSyntax(ownerUri, query);
}

View File

@@ -26,6 +26,10 @@ class ExtHostQueryDocument implements azdata.queryeditor.QueryDocument {
public createQueryTab(tab: azdata.window.DialogTab): void {
this._proxy.$createQueryTab(this.uri, tab.title, tab.content);
}
public connect(connectionProfile: azdata.connection.ConnectionProfile): Thenable<void> {
return this._proxy.$connectWithProfile(this.uri, connectionProfile);
}
}
export class ExtHostQueryEditor implements ExtHostQueryEditorShape {

View File

@@ -118,7 +118,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
registerConnectionEventListener(listener: azdata.connection.ConnectionEventListener): void {
return extHostConnectionManagement.$registerConnectionEventListener(mssqlProviderName, listener);
},
getConnection(uri: string): Thenable<azdata.connection.ConnectionProfile> {
return extHostConnectionManagement.$getConnection(uri);
},
// "sqlops" back-compat APIs
getActiveConnections(): Thenable<azdata.connection.Connection[]> {
console.warn('the method azdata.connection.getActiveConnections has been deprecated, replace it with azdata.connection.getConnections');

View File

@@ -180,6 +180,11 @@ export abstract class ExtHostDataProtocolShape {
*/
$setQueryExecutionOptions(handle: number, ownerUri: string, options: azdata.QueryExecutionOptions): Thenable<void> { throw ni(); }
/**
* Connect the editor document to the given profile
*/
$connectWithProfile(handle: number, ownerUri: string, profile: azdata.connection.ConnectionProfile): Thenable<void> { throw ni(); }
/**
* Disposes the cached information regarding a query
*/
@@ -578,6 +583,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
export interface MainThreadConnectionManagementShape extends IDisposable {
$registerConnectionEventListener(handle: number, providerId: string): void;
$getConnections(activeConnectionsOnly?: boolean): Thenable<azdata.connection.ConnectionProfile[]>;
$getConnection(uri: string): Thenable<azdata.connection.ConnectionProfile>;
$getActiveConnections(): Thenable<azdata.connection.Connection[]>;
$getCurrentConnection(): Thenable<azdata.connection.Connection>;
$getCurrentConnectionProfile(): Thenable<azdata.connection.ConnectionProfile>;
@@ -780,6 +786,7 @@ export interface ExtHostQueryEditorShape {
export interface MainThreadQueryEditorShape extends IDisposable {
$connect(fileUri: string, connectionId: string): Thenable<void>;
$connectWithProfile(fileUri: string, connectionProfile: azdata.connection.ConnectionProfile): Thenable<void>;
$runQuery(fileUri: string, runCurrentQuery?: boolean): void;
$createQueryTab(fileUri: string, title: string, content: string): void;
$setQueryExecutionOptions(fileUri: string, options: azdata.QueryExecutionOptions): Thenable<void>;

View File

@@ -564,25 +564,116 @@ export interface ISingleNotebookEditOperation {
}
export class ConnectionProfile {
get providerId(): string {
return this.options['providerId'];
}
providerId: string;
connectionId: string;
connectionName: string;
serverName: string;
databaseName: string;
userName: string;
password: string;
authenticationType: string;
savePassword: boolean;
groupFullName: string;
groupId: string;
saveProfile: boolean;
azureTenantId?: string;
options: { [name: string]: any };
set providerId(value: string) {
this.options['providerId'] = value;
}
static createFrom(options: any[]): ConnectionProfile {
// create from options
return undefined;
get connectionId(): string {
return this.options['connectionId'];
}
set connectionId(value: string) {
this.options['connectionId'] = value;
}
get connectionName(): string {
return this.options['connectionName'];
}
set connectionName(value: string) {
this.options['connectionName'] = value;
}
get serverName(): string {
return this.options['serverName'];
}
set serverName(value: string) {
this.options['serverName'] = value;
}
get databaseName(): string {
return this.options['databaseName'];
}
set databaseName(value: string) {
this.options['databaseName'] = value;
}
get userName(): string {
return this.options['userName'];
}
set userName(value: string) {
this.options['userName'] = value;
}
get password(): string {
return this.options['password'];
}
set password(value: string) {
this.options['password'] = value;
}
get authenticationType(): string {
return this.options['authenticationType'];
}
set authenticationType(value: string) {
this.options['authenticationType'] = value;
}
get savePassword(): boolean {
return this.options['savePassword'];
}
set savePassword(value: boolean) {
this.options['savePassword'] = value;
}
get groupFullName(): string {
return this.options['groupFullName'];
}
set groupFullName(value: string) {
this.options['groupFullName'] = value;
}
get groupId(): string {
return this.options['groupId'];
}
set groupId(value: string) {
this.options['groupId'] = value;
}
get saveProfile(): boolean {
return this.options['groupId'];
}
set saveProfile(value: boolean) {
this.options['groupId'] = value;
}
get azureTenantId(): string {
return this.options['azureTenantId'];
}
set azureTenantId(value: string) {
this.options['azureTenantId'] = value;
}
options: Map<string, any> = new Map<string, any>();
static createFrom(options: Map<string, any>): ConnectionProfile {
let profile = new ConnectionProfile();
profile.options = options;
return profile;
}
}

View File

@@ -472,12 +472,14 @@ export class ToggleConnectDatabaseAction extends QueryTaskbarAction {
}
public run(): Promise<void> {
if (this.connected) {
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
// determine if we need to disconnect, cancel an in-progress connection, or do nothing
this.connectionManagementService.disconnectEditor(this.editor.input);
} else {
this.connectEditor(this.editor);
if (!this.editor.input.isSharedSession) {
if (this.connected) {
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
// determine if we need to disconnect, cancel an in-progress connection, or do nothing
this.connectionManagementService.disconnectEditor(this.editor.input);
} else {
this.connectEditor(this.editor);
}
}
return Promise.resolve(null);
}

View File

@@ -355,4 +355,8 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
public get tabColor(): string {
return this._connectionManagementService.getTabColorForUri(this.uri);
}
public get isSharedSession(): boolean {
return this.uri && this.uri.startsWith('vsls:');
}
}