diff --git a/src/sql/platform/connection/common/connectionProfileGroup.ts b/src/sql/platform/connection/common/connectionProfileGroup.ts index 2cc1689b2c..8616b26bdf 100644 --- a/src/sql/platform/connection/common/connectionProfileGroup.ts +++ b/src/sql/platform/connection/common/connectionProfileGroup.ts @@ -108,7 +108,7 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro * Returns true if all connections in the tree have valid options using the correct capabilities */ public get hasValidConnections(): boolean { - let invalidConnections = this._childConnections.find(c => !c.isConnectionOptionsValid); + let invalidConnections = this._childConnections.find(c => c.serverCapabilities === undefined); if (invalidConnections !== undefined) { return false; } else { diff --git a/src/sql/platform/connection/common/providerConnectionInfo.ts b/src/sql/platform/connection/common/providerConnectionInfo.ts index 754aa5e724..64cc783548 100644 --- a/src/sql/platform/connection/common/providerConnectionInfo.ts +++ b/src/sql/platform/connection/common/providerConnectionInfo.ts @@ -10,6 +10,7 @@ import * as azdata from 'azdata'; import * as Constants from 'sql/platform/connection/common/constants'; import { ICapabilitiesService, ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService'; import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces'; +import { localize } from 'vs/nls'; type SettableProperty = 'serverName' | 'authenticationType' | 'databaseName' | 'password' | 'connectionName' | 'userName'; @@ -145,12 +146,15 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect } private getServerInfo() { - let title = this.serverName; - // Only show database name if the provider supports it. - if (this.serverCapabilities?.connectionOptions?.find(option => option.specialValueType === ConnectionOptionSpecialType.databaseName)) { - title += `, ${this.databaseName || ''}`; + let title = ''; + if (this.serverCapabilities) { + title = this.serverName; + // Only show database name if the provider supports it. + if (this.serverCapabilities.connectionOptions?.find(option => option.specialValueType === ConnectionOptionSpecialType.databaseName)) { + title += `, ${this.databaseName || ''}`; + } + title += ` (${this.userName || this.authenticationType})`; } - title += ` (${this.userName || this.authenticationType})`; return title; } @@ -160,10 +164,18 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect public get title(): string { let label = ''; - if (this.connectionName) { - label = this.connectionName; + if (this.serverCapabilities) { + if (this.connectionName) { + label = this.connectionName; + } else { + label = this.getServerInfo(); + } + } + // The provider capabilities are registered at the same time at load time, we can assume all providers are registered as long as the collection is not empty. + else if (Object.keys(this.capabilitiesService.providers).length > 0) { + return localize('connection.unsupported', "Unsupported connection"); } else { - label = this.getServerInfo(); + return localize('loading', "Loading..."); } return label; } @@ -172,13 +184,6 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect return this.getServerInfo(); } - /** - * Returns true if the capabilities and options are loaded correctly - */ - public get isConnectionOptionsValid(): boolean { - return !!this.serverCapabilities && this.title.indexOf('undefined') < 0; - } - public isPasswordRequired(): boolean { // if there is no provider capabilities metadata assume a password is not required if (!this.serverCapabilities) { diff --git a/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts b/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts index 373cddd959..dc443ddbea 100644 --- a/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts +++ b/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts @@ -107,6 +107,11 @@ export class OEManageConnectionAction extends Action { return true; } + if (!this._capabilitiesService.getCapabilities(connectionProfile.providerName)) { + this._connectionManagementService.handleUnsupportedProvider(connectionProfile.providerName); + return true; + } + let options: IConnectionCompletionOptions = { params: undefined, saveTheConnection: false, diff --git a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts index fa499bca3e..ca0ec2bc1b 100644 --- a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts +++ b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts @@ -42,6 +42,7 @@ import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objec import { coalesce } from 'vs/base/common/arrays'; import { CONNECTIONS_SORT_BY_CONFIG_KEY } from 'sql/platform/connection/common/connectionConfig'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { debounce } from 'vs/base/common/decorators'; export const CONTEXT_SERVER_TREE_VIEW = new RawContextKey('serverTreeView.view', ServerTreeViewView.all); export const CONTEXT_SERVER_TREE_HAS_CONNECTIONS = new RawContextKey('serverTreeView.hasConnections', false); @@ -67,7 +68,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView { @IThemeService private _themeService: IThemeService, @IErrorMessageService private _errorMessageService: IErrorMessageService, @IConfigurationService private _configurationService: IConfigurationService, - @ICapabilitiesService capabilitiesService: ICapabilitiesService, + @ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @IContextMenuService private _contextMenuService: IContextMenuService, @IKeybindingService private _keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService @@ -78,23 +79,28 @@ export class ServerTreeView extends Disposable implements IServerTreeView { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._onSelectionOrFocusChange = new Emitter(); this._actionProvider = this._instantiationService.createInstance(ServerTreeActionProvider); - capabilitiesService.onCapabilitiesRegistered(async () => { - if (this._tree instanceof AsyncServerTree) { - // Refresh the tree input now that the capabilities are registered so that we can - // get the full ConnectionProfiles with the server info updated properly - const treeInput = TreeUpdateUtils.getTreeInput(this._connectionManagementService)!; - await this._tree.setInput(treeInput); - this._treeSelectionHandler.onTreeActionStateChange(false); - } else { - if (this._connectionManagementService.hasRegisteredServers()) { - await this.refreshTree(); - this._treeSelectionHandler.onTreeActionStateChange(false); - } - } + this._capabilitiesService.onCapabilitiesRegistered(async () => { + await this.handleOnCapabilitiesRegistered(); }); this.registerCommands(); } + @debounce(50) + private async handleOnCapabilitiesRegistered(): Promise { + if (this._tree instanceof AsyncServerTree) { + // Refresh the tree input now that the capabilities are registered so that we can + // get the full ConnectionProfiles with the server info updated properly + const treeInput = TreeUpdateUtils.getTreeInput(this._connectionManagementService)!; + await this._tree.setInput(treeInput); + this._treeSelectionHandler.onTreeActionStateChange(false); + } else { + if (this._connectionManagementService.hasRegisteredServers()) { + await this.refreshTree(); + this._treeSelectionHandler.onTreeActionStateChange(false); + } + } + } + public get view(): ServerTreeViewView { return this._viewKey.get(); } @@ -532,7 +538,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView { } private onSelected(event: any): void { - this._treeSelectionHandler.onTreeSelect(event, this._tree!, this._connectionManagementService, this._objectExplorerService, () => this._onSelectionOrFocusChange.fire()); + this._treeSelectionHandler.onTreeSelect(event, this._tree!, this._connectionManagementService, this._objectExplorerService, this._capabilitiesService, () => this._onSelectionOrFocusChange.fire()); this._onSelectionOrFocusChange.fire(); } diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts index ec9e25e046..0a8a38215f 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts @@ -169,8 +169,12 @@ suite('ServerTreeView onAddConnectionProfile handler tests', () => { mockTree.verify(x => x.select(TypeMoq.It.isAny()), TypeMoq.Times.never()); }); - test('The tree refreshes when new capabilities are registered', () => { + test('The tree refreshes when new capabilities are registered', (done) => { capabilitiesService.fireCapabilitiesRegistered(undefined, undefined); - mockRefreshTreeMethod.verify(x => x(), TypeMoq.Times.once()); + // A debounce is added to the handler, we need to wait a bit before checking. + setTimeout(() => { + mockRefreshTreeMethod.verify(x => x(), TypeMoq.Times.once()); + done(); + }, 100); }); }); diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts index 78f42f4dc7..2aa1000db6 100644 --- a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts +++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts @@ -6,7 +6,6 @@ import 'vs/css!./media/objectTypes/objecttypes'; import * as dom from 'vs/base/browser/dom'; -import { localize } from 'vs/nls'; import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { ConnectionProfile, IconPath } from 'sql/platform/connection/common/connectionProfile'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; @@ -121,10 +120,6 @@ class ConnectionProfileTemplate extends Disposable { const iconPath: IconPath | undefined = getIconPath(element, this._connectionManagementService); renderServerIcon(this._icon, iconPath); let label = element.title; - if (!element.isConnectionOptionsValid) { - label = localize('loading', "Loading..."); - } - this._label.textContent = label; this._root.title = element.serverInfo; } diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts index 5d4bb52ab4..cfb81faab8 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts @@ -6,7 +6,6 @@ import 'vs/css!./media/objectTypes/objecttypes'; import * as dom from 'vs/base/browser/dom'; -import { localize } from 'vs/nls'; import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; @@ -227,10 +226,6 @@ export class ServerTreeRenderer implements IRenderer { this.renderServerIcon(templateData.icon, iconPath, isConnected); let label = connection.title; - if (!connection.isConnectionOptionsValid) { - label = localize('loading', "Loading..."); - } - templateData.label.textContent = label; templateData.root.title = connection.serverInfo; templateData.connectionProfile = connection; diff --git a/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts b/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts index 557410e6ce..ee1d125469 100644 --- a/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts +++ b/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts @@ -12,6 +12,8 @@ import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/br import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode'; import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils'; import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree'; +import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class TreeSelectionHandler { // progressRunner: IProgressRunner; @@ -47,14 +49,14 @@ export class TreeSelectionHandler { /** * Handle selection of tree element */ - public onTreeSelect(event: any, tree: AsyncServerTree | ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) { + public onTreeSelect(event: any, tree: AsyncServerTree | ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, capabilitiesService: ICapabilitiesService, connectionCompleteCallback: () => void) { let sendSelectionEvent = ((event: any, selection: any, isDoubleClick: boolean, userInteraction: boolean) => { // userInteraction: defensive - don't touch this something else is handling it. if (userInteraction === true && this._lastClicked && this._lastClicked[0] === selection[0]) { this._lastClicked = undefined; } if (!TreeUpdateUtils.isInDragAndDrop) { - this.handleTreeItemSelected(connectionManagementService, objectExplorerService, isDoubleClick, this.isKeyboardEvent(event), selection, tree, connectionCompleteCallback); + this.handleTreeItemSelected(connectionManagementService, objectExplorerService, capabilitiesService, isDoubleClick, this.isKeyboardEvent(event), selection, tree, connectionCompleteCallback); } }); @@ -94,15 +96,20 @@ export class TreeSelectionHandler { * * @param connectionManagementService * @param objectExplorerService + * @param capabilitiesService * @param isDoubleClick * @param isKeyboard * @param selection * @param tree * @param connectionCompleteCallback A function that gets called after a connection is established due to the selection, if needed */ - private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: AsyncServerTree | ITree, connectionCompleteCallback: () => void): void { + private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, capabilitiesService: ICapabilitiesService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: AsyncServerTree | ITree, connectionCompleteCallback: () => void): void { if (tree instanceof AsyncServerTree) { if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) { + if (!capabilitiesService.getCapabilities(selection[0].providerName)) { + connectionManagementService.handleUnsupportedProvider(selection[0].providerName).catch(onUnexpectedError); + return; + } this.onTreeActionStateChange(true); } } else { @@ -116,6 +123,10 @@ export class TreeSelectionHandler { }; if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) { connectionProfile = selection[0]; + if (!capabilitiesService.getCapabilities(connectionProfile.providerName)) { + connectionManagementService.handleUnsupportedProvider(connectionProfile.providerName).catch(onUnexpectedError); + return; + } if (connectionProfile) { this.onTreeActionStateChange(true);