From 1d4398388c15f278630aa222a3c8aa4935cbfc6f Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Wed, 28 Oct 2020 13:48:28 -0700 Subject: [PATCH] bring Azure context menu to the new tree and enhance start cloud shell command (#13101) * bring context menu to the new tree * enhance start cloud shell command * text --- extensions/azurecore/package.json | 10 +++ .../azurecore/src/azureResource/commands.ts | 33 ++++++-- .../connection/browser/connectionBrowseTab.ts | 81 +++++++++++-------- 3 files changed, 86 insertions(+), 38 deletions(-) diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index bdfa5df7a8..c80bcc0101 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -282,6 +282,16 @@ "command": "azure.resource.refresh", "when": "contextValue == azure.resource.itemType.account", "group": "navigation" + }, + { + "command": "azure.resource.signin", + "when": "treeId == connectionDialog/azureResourceExplorer", + "group": "navigation" + }, + { + "command": "azure.resource.refreshall", + "when": "treeId == connectionDialog/azureResourceExplorer", + "group": "navigation" } ] }, diff --git a/extensions/azurecore/src/azureResource/commands.ts b/extensions/azurecore/src/azureResource/commands.ts index 241339f6b0..0e972c4982 100644 --- a/extensions/azurecore/src/azureResource/commands.ts +++ b/extensions/azurecore/src/azureResource/commands.ts @@ -30,14 +30,35 @@ export function registerAzureResourceCommands(appContext: AppContext, trees: (Az vscode.window.showInformationMessage(msg); return; } - if (!node || !(node instanceof AzureResourceAccountTreeNode)) { - return; + let azureAccount: AzureAccount | undefined; + if (node instanceof AzureResourceAccountTreeNode) { + azureAccount = node.account as AzureAccount; + } else { + let accounts = await azdata.accounts.getAllAccounts(); + accounts = accounts.filter(a => a.key.providerId.startsWith('azure')); + if (accounts.length === 0) { + const signin = localize('azure.signIn', "Sign in"); + const action = await vscode.window.showErrorMessage(localize('azure.noAccountError', "You are not currently signed into any Azure accounts, Please sign in and then try again."), + signin); + if (action === signin) { + vscode.commands.executeCommand('azure.resource.signin'); + } + return; + } else if (accounts.length === 1) { + azureAccount = accounts[0]; + } else { + const pickedAccount = await vscode.window.showQuickPick(accounts.map(account => account.displayInfo.displayName), { + canPickMany: false, + placeHolder: localize('azure.pickAnAzureAccount', "Select an Azure account") + }); + if (!pickedAccount) { + vscode.window.showErrorMessage(localize('azure.accountNotSelectedError', "You must select an Azure account for this feature to work.")); + return; + } + azureAccount = accounts.find(acct => acct.displayInfo.displayName === pickedAccount); + } } - const accountNode = node as AzureResourceAccountTreeNode; - const azureAccount = accountNode.account as AzureAccount; - - const terminalService = appContext.getService(AzureResourceServiceNames.terminalService); const listOfTenants = azureAccount.properties.tenants.map(t => t.displayName); diff --git a/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts b/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts index 0386b0e372..511248ecdf 100644 --- a/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts +++ b/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts @@ -47,7 +47,7 @@ import { IAction } from 'vs/base/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; -export type TreeElement = ConnectionProviderElement | ITreeItemFromProvider | SavedConnectionNode | ServerTreeElement; +export type TreeElement = ConnectionDialogTreeProviderElement | ITreeItemFromProvider | SavedConnectionNode | ServerTreeElement; export class ConnectionBrowseTab implements IPanelTab { public readonly title = localize('connectionDialog.browser', "Browse"); @@ -156,9 +156,17 @@ export class ConnectionBrowserView extends Disposable implements IPanelView { accessibilityProvider: new ListAccessibilityProvider() }) as WorkbenchAsyncDataTree); this._register(this.tree.onContextMenu(e => { - const context = e.element as ITreeItemFromProvider; - if (context?.element) { - this.contextKey.set(context.element); + let context: ITreeItem | ConnectionDialogTreeProviderElement | undefined; + let actionContext: TreeViewItemHandleArg | ConnectionDialogTreeProviderElement | undefined; + if (instanceOfITreeItemFromProvider(e.element)) { + context = e.element.element; + actionContext = { $treeViewId: e.element.treeId, $treeItemHandle: context.handle, $treeItem: context }; + } else if (e.element instanceof ConnectionDialogTreeProviderElement) { + context = e.element; + actionContext = e.element; + } + if (context) { + this.contextKey.set(context); const menu = this.menuService.createMenu(MenuId.ConnectionDialogBrowseTreeContext, this.contextKeyService); const primary: IAction[] = []; const secondary: IAction[] = []; @@ -168,7 +176,7 @@ export class ConnectionBrowserView extends Disposable implements IPanelView { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => result.primary, - getActionsContext: () => ({ $treeViewId: context.treeId, $treeItemHandle: context.element.handle, $treeItem: context.element }) + getActionsContext: () => actionContext }); } })); @@ -239,7 +247,11 @@ export interface ITreeItemFromProvider { getChildren?(): Promise } -class ConnectionProviderElement { +export function instanceOfITreeItemFromProvider(obj: any): obj is ITreeItemFromProvider { + return !!(obj)?.element; +} + +class ConnectionDialogTreeProviderElement { public readonly id = this.descriptor.id; public readonly name = this.descriptor.name; @@ -262,7 +274,7 @@ class ListDelegate implements IListVirtualDelegate { } getTemplateId(element: TreeElement): string { - if (element instanceof ConnectionProviderElement) { + if (element instanceof ConnectionDialogTreeProviderElement) { return ProviderElementRenderer.TEMPLATE_ID; } else if (element instanceof ConnectionProfile) { return ServerTreeRenderer.CONNECTION_TEMPLATE_ID; @@ -283,7 +295,7 @@ interface ProviderElementTemplate { readonly name: HTMLElement; } -class ProviderElementRenderer implements ITreeRenderer { +class ProviderElementRenderer implements ITreeRenderer { public static readonly TEMPLATE_ID = 'ProviderElementTemplate'; public readonly templateId = ProviderElementRenderer.TEMPLATE_ID; @@ -293,7 +305,7 @@ class ProviderElementRenderer implements ITreeRenderer, index: number, templateData: ProviderElementTemplate, height: number): void { + renderElement(element: ITreeNode, index: number, templateData: ProviderElementTemplate, height: number): void { templateData.name.innerText = element.element.name; } @@ -306,7 +318,7 @@ interface SavedConnectionNodeElementTemplate { readonly name: HTMLElement; } -class SavedConnectionsNodeRenderer implements ITreeRenderer { +class SavedConnectionsNodeRenderer implements ITreeRenderer { public static readonly TEMPLATE_ID = 'savedConnectionNode'; public readonly templateId = SavedConnectionsNodeRenderer.TEMPLATE_ID; @@ -316,7 +328,7 @@ class SavedConnectionsNodeRenderer implements ITreeRenderer, index: number, templateData: SavedConnectionNodeElementTemplate, height: number): void { + renderElement(element: ITreeNode, index: number, templateData: SavedConnectionNodeElementTemplate, height: number): void { templateData.name.innerText = localize('savedConnections', "Saved Connections"); } @@ -326,7 +338,7 @@ class SavedConnectionsNodeRenderer implements ITreeRenderer { getId(element: TreeElement): string { - if (element instanceof ConnectionProviderElement) { + if (element instanceof ConnectionDialogTreeProviderElement) { return element.id; } else if (element instanceof ConnectionProfile) { return element.id; @@ -353,7 +365,7 @@ class TreeModel { getChildren(): TreeElement[] { this._savedConnectionNode = this.instantiationService.createInstance(SavedConnectionNode); const descriptors = Array.from(this.connectionTreeService.descriptors); - return [this._savedConnectionNode, ...Iterable.map(this.connectionTreeService.providers, ([id, provider]) => new ConnectionProviderElement(provider, descriptors.find(i => i.id === id)))]; + return [this._savedConnectionNode, ...Iterable.map(this.connectionTreeService.providers, ([id, provider]) => new ConnectionDialogTreeProviderElement(provider, descriptors.find(i => i.id === id)))]; } public get savedConnectionNode(): SavedConnectionNode | undefined { @@ -363,7 +375,7 @@ class TreeModel { class ListAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: TreeElement): string { - if (element instanceof ConnectionProviderElement) { + if (element instanceof ConnectionDialogTreeProviderElement) { return element.name; } else if (element instanceof ConnectionProfile) { return element.serverName; @@ -394,7 +406,7 @@ class DataSource implements IAsyncDataSource { hasChildren(element: TreeModel | TreeElement): boolean { if (element instanceof TreeModel) { return true; - } else if (element instanceof ConnectionProviderElement) { + } else if (element instanceof ConnectionDialogTreeProviderElement) { return true; } else if (element instanceof ConnectionProfile) { return false; @@ -413,12 +425,7 @@ class DataSource implements IAsyncDataSource { public get expandableTreeNodes(): TreeElement[] { return this.treeNodes.filter(node => { - return node instanceof TreeModel - || node instanceof SavedConnectionNode - || node instanceof ConnectionProfileGroup - || node instanceof TreeNode - || node instanceof ConnectionProviderElement - || (!(node instanceof ConnectionProfile) && node.element.collapsibleState !== TreeItemCollapsibleState.None); + return instanceOfITreeItemFromProvider(node) && node.element.collapsibleState !== TreeItemCollapsibleState.None; }); } @@ -436,7 +443,7 @@ class DataSource implements IAsyncDataSource { } else if ( !(element instanceof TreeModel) && !(element instanceof TreeNode) && - !(element instanceof ConnectionProviderElement) + !(element instanceof ConnectionDialogTreeProviderElement) ) { children = (children as ITreeItemFromProvider[]).filter(item => { return item.element.collapsibleState !== TreeItemCollapsibleState.None || this._filterRegex.test(item.element.label.label); @@ -599,26 +606,36 @@ class TreeItemRenderer extends Disposable implements ITreeRenderer { - static readonly ContextValue = new RawContextKey('contextValue', undefined); - static readonly Item = new RawContextKey('item', undefined); - private _contextValueKey: IContextKey; - private _itemKey: IContextKey; +type ContextValueType = ITreeItem | ConnectionDialogTreeProviderElement; + +class ContextKey extends Disposable implements IContextKey { + static readonly ContextValue = new RawContextKey('contextValue', undefined); + static readonly TreeId = new RawContextKey('treeId', undefined); + private _contextValueKey: IContextKey; + private _treeIdKey: IContextKey; + private _item: ContextValueType; constructor( @IContextKeyService contextKeyService: IContextKeyService ) { super(); this._contextValueKey = ContextKey.ContextValue.bindTo(contextKeyService); - this._itemKey = ContextKey.Item.bindTo(contextKeyService); + this._treeIdKey = ContextKey.TreeId.bindTo(contextKeyService); } - set(value: ITreeItem): void { - this._contextValueKey.set(value.contextValue); + set(value: ContextValueType): void { + this.reset(); + this._item = value; + if (value instanceof ConnectionDialogTreeProviderElement) { + this._treeIdKey.set(value.id); + } else { + this._contextValueKey.set(value.contextValue); + } } reset(): void { this._contextValueKey.reset(); + this._treeIdKey.reset(); } - get(): ITreeItem { - return this._itemKey.get(); + get(): ContextValueType { + return this._item; } }