handle unsupported connections in OE/Recent connections view (#20588)

* handle unknown provider in OE

* more update

* add comment

* test
This commit is contained in:
Alan Ren
2022-09-12 11:48:08 -07:00
committed by GitHub
parent b5408495b9
commit 6015c8e2f4
8 changed files with 67 additions and 46 deletions

View File

@@ -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 {

View File

@@ -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 || '<default>'}`;
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 || '<default>'}`;
}
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) {

View File

@@ -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,

View File

@@ -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<ServerTreeViewView>('serverTreeView.view', ServerTreeViewView.all);
export const CONTEXT_SERVER_TREE_HAS_CONNECTIONS = new RawContextKey<boolean>('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<void> {
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();
}

View File

@@ -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);
});
});

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 = <ConnectionProfile>selection[0];
if (!capabilitiesService.getCapabilities(connectionProfile.providerName)) {
connectionManagementService.handleUnsupportedProvider(connectionProfile.providerName).catch(onUnexpectedError);
return;
}
if (connectionProfile) {
this.onTreeActionStateChange(true);