diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index ed9d84d429..b6e4e66756 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -113,16 +113,6 @@ "title": "%accounts.clearTokenCache%", "category": "Azure Accounts" }, - { - "command": "azure.accounts.getSubscriptions", - "title": "%azure.accounts.getSubscriptions.title%", - "category": "Azure Accounts" - }, - { - "command": "azure.accounts.getResourceGroups", - "title": "%azure.accounts.getResourceGroups.title%", - "category": "Azure Accounts" - }, { "command": "azure.resource.signin", "title": "%azure.resource.signin.title%", @@ -198,14 +188,6 @@ { "command": "azure.resource.connectsqlserver", "when": "false" - }, - { - "command": "azure.accounts.getSubscriptions", - "when": "false" - }, - { - "command": "azure.accounts.getResourceGroups", - "when": "false" } ], "view/title": [ diff --git a/extensions/azurecore/package.nls.json b/extensions/azurecore/package.nls.json index 737d01cef1..f7cc00e140 100644 --- a/extensions/azurecore/package.nls.json +++ b/extensions/azurecore/package.nls.json @@ -15,8 +15,6 @@ "azure.resource.connectsqldb.title": "Add to Servers", "accounts.clearTokenCache": "Clear Azure Account Token Cache", - "azure.accounts.getSubscriptions.title": "Get Azure Account Subscriptions", - "azure.accounts.getResourceGroups.title": "Get Azure Account Subscription Resource Groups", "azure.openInAzurePortal.title": "Open in Azure Portal", diff --git a/extensions/azurecore/src/azureResource/commands.ts b/extensions/azurecore/src/azureResource/commands.ts index edc5eb1784..371604b171 100644 --- a/extensions/azurecore/src/azureResource/commands.ts +++ b/extensions/azurecore/src/azureResource/commands.ts @@ -18,9 +18,6 @@ import { AzureResourceTreeProvider } from './tree/treeProvider'; import { AzureResourceAccountTreeNode } from './tree/accountTreeNode'; import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureTerminalService } from '../azureResource/interfaces'; import { AzureResourceServiceNames } from './constants'; -import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService'; -import { GetSubscriptionsResult, GetResourceGroupsResult } from '../azurecore'; -import { isArray } from 'util'; import { AzureAccount, Tenant } from '../account-provider/interfaces'; export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void { @@ -75,79 +72,6 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur } }); - // Resource Management commands - appContext.apiWrapper.registerCommand('azure.accounts.getSubscriptions', async (account?: azdata.Account, ignoreErrors: boolean = false): Promise => { - const result: GetSubscriptionsResult = { subscriptions: [], errors: [] }; - if (!account?.properties?.tenants || !isArray(account.properties.tenants)) { - const error = new Error(localize('azure.accounts.getSubscriptions.invalidParamsError', "Invalid account")); - if (!ignoreErrors) { - throw error; - } - result.errors.push(error); - return result; - } - const subscriptionService = appContext.getService(AzureResourceServiceNames.subscriptionService); - const tokens = await appContext.apiWrapper.getSecurityToken(account, azdata.AzureResource.ResourceManagement); - await Promise.all(account.properties.tenants.map(async (tenant: { id: string | number; }) => { - try { - const token = tokens[tenant.id].token; - const tokenType = tokens[tenant.id].tokenType; - - result.subscriptions.push(...await subscriptionService.getSubscriptions(account, new TokenCredentials(token, tokenType))); - } catch (err) { - const error = new Error(localize('azure.accounts.getSubscriptions.queryError', "Error fetching subscriptions for account {0} tenant {1} : {2}", - account.displayInfo.displayName, - tenant.id, - err instanceof Error ? err.message : err)); - console.warn(error); - if (!ignoreErrors) { - throw error; - } - result.errors.push(error); - } - return Promise.resolve(); - })); - return result; - }); - - appContext.apiWrapper.registerCommand('azure.accounts.getResourceGroups', async (account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors: boolean = false): Promise => { - const result: GetResourceGroupsResult = { resourceGroups: [], errors: [] }; - if (!account?.properties?.tenants || !isArray(account.properties.tenants) || !subscription) { - const error = new Error(localize('azure.accounts.getResourceGroups.invalidParamsError', "Invalid account or subscription")); - if (!ignoreErrors) { - throw error; - } - result.errors.push(error); - return result; - } - const service = new AzureResourceGroupService(); - await Promise.all(account.properties.tenants.map(async (tenant: { id: string | number; }) => { - try { - const tokens = await appContext.apiWrapper.getSecurityToken(account, azdata.AzureResource.ResourceManagement); - const token = tokens[tenant.id].token; - const tokenType = tokens[tenant.id].tokenType; - - result.resourceGroups.push(...await service.getResources(subscription, new TokenCredentials(token, tokenType), account)); - } catch (err) { - const error = new Error(localize('azure.accounts.getResourceGroups.queryError', "Error fetching resource groups for account {0} ({1}) subscription {2} ({3}) tenant {4} : {5}", - account.displayInfo.displayName, - account.displayInfo.userId, - subscription.id, - subscription.name, - tenant.id, - err instanceof Error ? err.message : err)); - console.warn(error); - if (!ignoreErrors) { - throw error; - } - result.errors.push(error); - } - return Promise.resolve(); - })); - - return result; - }); - // Resource Tree commands appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => { diff --git a/extensions/azurecore/src/azureResource/constants.ts b/extensions/azurecore/src/azureResource/constants.ts index bea9844b36..aaeca8a73f 100644 --- a/extensions/azurecore/src/azureResource/constants.ts +++ b/extensions/azurecore/src/azureResource/constants.ts @@ -16,6 +16,7 @@ export enum AzureResourceItemType { export enum AzureResourceServiceNames { resourceService = 'AzureResourceService', + resourceGroupService = 'AzureResourceGroupService', cacheService = 'AzureResourceCacheService', accountService = 'AzureResourceAccountService', subscriptionService = 'AzureResourceSubscriptionService', diff --git a/extensions/azurecore/src/azureResource/utils.ts b/extensions/azurecore/src/azureResource/utils.ts index 910371714d..ecadcc4a59 100644 --- a/extensions/azurecore/src/azureResource/utils.ts +++ b/extensions/azurecore/src/azureResource/utils.ts @@ -3,7 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as azdata from 'azdata'; import * as nls from 'vscode-nls'; +import { azureResource } from './azure-resource'; +import { GetResourceGroupsResult, GetSubscriptionsResult } from '../azurecore'; +import { isArray } from 'util'; +import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService'; +import { TokenCredentials } from '@azure/ms-rest-js'; +import { AppContext } from '../appContext'; +import { IAzureResourceSubscriptionService } from './interfaces'; +import { AzureResourceServiceNames } from './constants'; + const localize = nls.loadMessageBundle(); function getErrorMessage(error: Error | string): string { @@ -92,3 +102,73 @@ export function equals(one: any, other: any): boolean { } return true; } + +export async function getResourceGroups(appContext: AppContext, account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors: boolean = false): Promise { + const result: GetResourceGroupsResult = { resourceGroups: [], errors: [] }; + if (!account?.properties?.tenants || !isArray(account.properties.tenants) || !subscription) { + const error = new Error(localize('azure.accounts.getResourceGroups.invalidParamsError', "Invalid account or subscription")); + if (!ignoreErrors) { + throw error; + } + result.errors.push(error); + return result; + } + const service = appContext.getService(AzureResourceServiceNames.resourceGroupService); + await Promise.all(account.properties.tenants.map(async (tenant: { id: string | number; }) => { + try { + const tokens = await azdata.accounts.getSecurityToken(account, azdata.AzureResource.ResourceManagement); + const token = tokens[tenant.id].token; + const tokenType = tokens[tenant.id].tokenType; + + result.resourceGroups.push(...await service.getResources(subscription, new TokenCredentials(token, tokenType), account)); + } catch (err) { + const error = new Error(localize('azure.accounts.getResourceGroups.queryError', "Error fetching resource groups for account {0} ({1}) subscription {2} ({3}) tenant {4} : {5}", + account.displayInfo.displayName, + account.displayInfo.userId, + subscription.id, + subscription.name, + tenant.id, + err instanceof Error ? err.message : err)); + console.warn(error); + if (!ignoreErrors) { + throw error; + } + result.errors.push(error); + } + })); + return result; +} + +export async function getSubscriptions(appContext: AppContext, account?: azdata.Account, ignoreErrors: boolean = false): Promise { + const result: GetSubscriptionsResult = { subscriptions: [], errors: [] }; + if (!account?.properties?.tenants || !isArray(account.properties.tenants)) { + const error = new Error(localize('azure.accounts.getSubscriptions.invalidParamsError', "Invalid account")); + if (!ignoreErrors) { + throw error; + } + result.errors.push(error); + return result; + } + + const subscriptionService = appContext.getService(AzureResourceServiceNames.subscriptionService); + const tokens = await appContext.apiWrapper.getSecurityToken(account, azdata.AzureResource.ResourceManagement); + await Promise.all(account.properties.tenants.map(async (tenant: { id: string | number; }) => { + try { + const token = tokens[tenant.id].token; + const tokenType = tokens[tenant.id].tokenType; + + result.subscriptions.push(...await subscriptionService.getSubscriptions(account, new TokenCredentials(token, tokenType))); + } catch (err) { + const error = new Error(localize('azure.accounts.getSubscriptions.queryError', "Error fetching subscriptions for account {0} tenant {1} : {2}", + account.displayInfo.displayName, + tenant.id, + err instanceof Error ? err.message : err)); + console.warn(error); + if (!ignoreErrors) { + throw error; + } + result.errors.push(error); + } + })); + return result; +} diff --git a/extensions/azurecore/src/azurecore.d.ts b/extensions/azurecore/src/azurecore.d.ts index d4d671717b..763dbdaf2d 100644 --- a/extensions/azurecore/src/azurecore.d.ts +++ b/extensions/azurecore/src/azurecore.d.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as azdata from 'azdata'; import { azureResource } from './azureResource/azure-resource'; /** @@ -62,6 +63,8 @@ export const enum AzureRegion { } export interface IExtension { + getSubscriptions(account?: azdata.Account, ignoreErrors?: boolean): Thenable; + getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Thenable; /** * Converts a region value (@see AzureRegion) into the localized Display Name * @param region The region value diff --git a/extensions/azurecore/src/extension.ts b/extensions/azurecore/src/extension.ts index a6264d5ac2..49f346d10c 100644 --- a/extensions/azurecore/src/extension.ts +++ b/extensions/azurecore/src/extension.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { promises as fs } from 'fs'; import * as path from 'path'; @@ -37,8 +38,10 @@ import { PostgresServerArcProvider } from './azureResource/providers/postgresArc import { PostgresServerArcService } from './azureResource/providers/postgresArcServer/postgresServerService'; import { azureResource } from './azureResource/azure-resource'; import * as azurecore from './azurecore'; +import * as azureResourceUtils from './azureResource/utils'; import * as utils from './utils'; import * as loc from './localizedConstants'; +import { AzureResourceGroupService } from './azureResource/providers/resourceGroup/resourceGroupService'; let extensionContext: vscode.ExtensionContext; @@ -84,6 +87,8 @@ export async function activate(context: vscode.ExtensionContext): Promise { return azureResourceUtils.getSubscriptions(appContext, account, ignoreErrors); }, + getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Thenable { return azureResourceUtils.getResourceGroups(appContext, account, subscription, ignoreErrors); }, provideResources(): azureResource.IAzureResourceProvider[] { const arcFeaturedEnabled = apiWrapper.getExtensionConfiguration().get('enableArcFeatures'); const providers: azureResource.IAzureResourceProvider[] = [ @@ -144,6 +149,7 @@ async function initAzureAccountProvider(extensionContext: vscode.ExtensionContex function registerAzureServices(appContext: AppContext): void { appContext.registerService(AzureResourceServiceNames.resourceService, new AzureResourceService()); + appContext.registerService(AzureResourceServiceNames.resourceGroupService, new AzureResourceGroupService()); appContext.registerService(AzureResourceServiceNames.accountService, new AzureResourceAccountService(appContext.apiWrapper)); appContext.registerService(AzureResourceServiceNames.cacheService, new AzureResourceCacheService(extensionContext)); appContext.registerService(AzureResourceServiceNames.subscriptionService, new AzureResourceSubscriptionService()); diff --git a/extensions/machine-learning/src/common/apiWrapper.ts b/extensions/machine-learning/src/common/apiWrapper.ts index 6f3904682f..85a2efb1bc 100644 --- a/extensions/machine-learning/src/common/apiWrapper.ts +++ b/extensions/machine-learning/src/common/apiWrapper.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import * as azdata from 'azdata'; +import * as azurecore from '../../../azurecore/src/azurecore'; /** * Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into @@ -137,4 +138,16 @@ export class ApiWrapper { public registerWidget(widgetId: string, handler: (view: azdata.ModelView) => void): void { azdata.ui.registerModelViewProvider(widgetId, handler); } + + private azurecoreApi: azurecore.IExtension | undefined; + + public async getAzurecoreApi(): Promise { + if (!this.azurecoreApi) { + this.azurecoreApi = await this.getExtension(azurecore.extension.name)?.activate(); + if (!this.azurecoreApi) { + throw new Error('Unable to retrieve azurecore API'); + } + } + return this.azurecoreApi; + } } diff --git a/extensions/machine-learning/src/modelManagement/azureModelRegistryService.ts b/extensions/machine-learning/src/modelManagement/azureModelRegistryService.ts index 96128007cf..0c1c46290e 100644 --- a/extensions/machine-learning/src/modelManagement/azureModelRegistryService.ts +++ b/extensions/machine-learning/src/modelManagement/azureModelRegistryService.ts @@ -52,7 +52,7 @@ export class AzureModelRegistryService { * @param account azure account */ public async getSubscriptions(account: azdata.Account | undefined): Promise { - const data = await this._apiWrapper.executeCommand(constants.azureSubscriptionsCommand, account, true); + const data: azureResource.GetSubscriptionsResult = await (await this._apiWrapper.getAzurecoreApi()).getSubscriptions(account, true); return data?.subscriptions; } @@ -64,7 +64,7 @@ export class AzureModelRegistryService { public async getGroups( account: azdata.Account | undefined, subscription: azureResource.AzureResourceSubscription | undefined): Promise { - const data = await this._apiWrapper.executeCommand(constants.azureResourceGroupsCommand, account, subscription, true); + const data: azureResource.GetResourceGroupsResult = await (await this._apiWrapper.getAzurecoreApi()).getResourceGroups(account, subscription, true); return data?.resourceGroups; } diff --git a/extensions/machine-learning/src/test/modelManagement/azureModelRegistryService.test.ts b/extensions/machine-learning/src/test/modelManagement/azureModelRegistryService.test.ts index e2a92f9169..a32e30702e 100644 --- a/extensions/machine-learning/src/test/modelManagement/azureModelRegistryService.test.ts +++ b/extensions/machine-learning/src/test/modelManagement/azureModelRegistryService.test.ts @@ -18,6 +18,7 @@ import { Workspace, WorkspacesListByResourceGroupResponse } from '@azure/arm-mac import { WorkspaceModel, AssetsQueryByIdResponse, Asset, GetArtifactContentInformation2Response } from '../../modelManagement/interfaces'; import { AzureMachineLearningWorkspaces, Workspaces } from '@azure/arm-machinelearningservices'; import { WorkspaceModels } from '../../modelManagement/workspacesModels'; +import { AzurecoreApiStub } from '../stubs'; interface TestContext { @@ -129,7 +130,9 @@ describe('AzureModelRegistryService', () => { testContext.config.object, testContext.httpClient.object, testContext.outputChannel); - testContext.apiWrapper.setup(x => x.executeCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ subscriptions: expected, errors: [] })); + const azurecoreApi = TypeMoq.Mock.ofType(AzurecoreApiStub); + azurecoreApi.setup(x => x.getSubscriptions(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ subscriptions: expected, errors: [] })); + testContext.apiWrapper.setup(x => x.getAzurecoreApi()).returns(() => Promise.resolve(azurecoreApi.object)); let actual = await service.getSubscriptions(testContext.accounts[0]); should.deepEqual(actual, expected); }); @@ -142,7 +145,9 @@ describe('AzureModelRegistryService', () => { testContext.config.object, testContext.httpClient.object, testContext.outputChannel); - testContext.apiWrapper.setup(x => x.executeCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ resourceGroups: expected, errors: [] })); + const azurecoreApi = TypeMoq.Mock.ofType(AzurecoreApiStub); + azurecoreApi.setup(x => x.getResourceGroups(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ resourceGroups: expected, errors: [] })); + testContext.apiWrapper.setup(x => x.getAzurecoreApi()).returns(() => Promise.resolve(azurecoreApi.object)); let actual = await service.getGroups(testContext.accounts[0], testContext.subscriptions[0]); should.deepEqual(actual, expected); }); @@ -188,15 +193,15 @@ describe('AzureModelRegistryService', () => { it('downloadModel should download model artifact successfully', async function (): Promise { let testContext = createContext(); const asset: Asset = - { - id: '1', - name: 'asset', - artifacts: [ - { - id: '/1/2/3/4/5/' - } - ] - }; + { + id: '1', + name: 'asset', + artifacts: [ + { + id: '/1/2/3/4/5/' + } + ] + }; const assetResponse: AssetsQueryByIdResponse = Object.assign(asset, { _response: undefined! }); diff --git a/extensions/machine-learning/src/test/stubs.ts b/extensions/machine-learning/src/test/stubs.ts new file mode 100644 index 0000000000..408f7db6c3 --- /dev/null +++ b/extensions/machine-learning/src/test/stubs.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; +import * as azurecore from '../../../azurecore/src/azurecore'; +import { azureResource } from '../../../azurecore/src/azureResource/azure-resource'; + +export class AzurecoreApiStub implements azurecore.IExtension { + getSubscriptions(_account?: azdata.Account | undefined, _ignoreErrors?: boolean | undefined): Thenable { + throw new Error('Method not implemented.'); + } + getResourceGroups(_account?: azdata.Account | undefined, _subscription?: azureResource.AzureResourceSubscription | undefined, _ignoreErrors?: boolean | undefined): Thenable { + throw new Error('Method not implemented.'); + } + getRegionDisplayName(_region?: string | undefined): string { + throw new Error('Method not implemented.'); + } + provideResources(): azureResource.IAzureResourceProvider[] { + throw new Error('Method not implemented.'); + } + +} diff --git a/extensions/resource-deployment/src/services/apiService.ts b/extensions/resource-deployment/src/services/apiService.ts new file mode 100644 index 0000000000..4e81006c9d --- /dev/null +++ b/extensions/resource-deployment/src/services/apiService.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azurecore from '../../../azurecore/src/azurecore'; +import * as vscode from 'vscode'; + +export interface IApiService { + getAzurecoreApi(): Promise; +} + +class ApiService implements IApiService { + + private azurecoreApi: azurecore.IExtension | undefined; + + constructor() { } + + public async getAzurecoreApi(): Promise { + if (!this.azurecoreApi) { + this.azurecoreApi = (await vscode.extensions.getExtension(azurecore.extension.name)?.activate()); + if (!this.azurecoreApi) { + throw new Error('Unable to retrieve azurecore API'); + } + } + return this.azurecoreApi; + } +} + +export const apiService: IApiService = new ApiService(); diff --git a/extensions/resource-deployment/src/ui/modelViewUtils.ts b/extensions/resource-deployment/src/ui/modelViewUtils.ts index 689ac83418..f3aaa76b6d 100644 --- a/extensions/resource-deployment/src/ui/modelViewUtils.ts +++ b/extensions/resource-deployment/src/ui/modelViewUtils.ts @@ -16,6 +16,7 @@ import { assert, getDateTimeString, getErrorMessage } from '../utils'; import { WizardInfoBase } from './../interfaces'; import { Model } from './model'; import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder'; +import { apiService } from '../services/apiService'; const localize = nls.loadMessageBundle(); @@ -839,16 +840,24 @@ async function handleSelectedAccountChanged( } try { - const response = await vscode.commands.executeCommand('azure.accounts.getSubscriptions', selectedAccount, true /*ignoreErrors*/); + const response = await (await apiService.getAzurecoreApi()).getSubscriptions(selectedAccount, true); if (!response) { return; } if (response.errors.length > 0) { - context.container.message = { - text: response.errors.join(EOL) || '', - description: '', - level: azdata.window.MessageLevel.Warning - }; + // If we got back some subscriptions then don't display the errors to the user - it's normal for users + // to not necessarily have access to all subscriptions on an account so displaying the errors + // in that case is usually just distracting and causes confusion + const errMsg = response.errors.join(EOL); + if (response.subscriptions.length === 0) { + context.container.message = { + text: errMsg || '', + description: '', + level: azdata.window.MessageLevel.Error + }; + } else { + console.log(errMsg); + } } subscriptionDropdown.values = response.subscriptions.map(subscription => { const displayName = `${subscription.name} (${subscription.id})`; @@ -906,17 +915,24 @@ async function handleSelectedSubscriptionChanged(context: AzureAccountFieldConte return; } try { - const response = await vscode.commands.executeCommand('azure.accounts.getResourceGroups', selectedAccount, selectedSubscription, true /*ignoreErrors*/); - //.then(response => { + const response = await (await apiService.getAzurecoreApi()).getResourceGroups(selectedAccount, selectedSubscription, true); if (!response) { return; } if (response.errors.length > 0) { - context.container.message = { - text: response.errors.join(EOL) || '', - description: '', - level: azdata.window.MessageLevel.Warning - }; + // If we got back some RG's then don't display the errors to the user - it's normal for users + // to not necessarily have access to all RG's on a subscription so displaying the errors + // in that case is usually just distracting and causes confusion + const errMsg = response.errors.join(EOL); + if (response.resourceGroups.length === 0) { + context.container.message = { + text: errMsg || '', + description: '', + level: azdata.window.MessageLevel.Error + }; + } else { + console.log(errMsg); + } } resourceGroupDropdown.values = (response.resourceGroups.length !== 0) ? response.resourceGroups.map(resourceGroup => resourceGroup.name).sort((a: string, b: string) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase())) diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts index d050496a53..63efdc4dc9 100644 --- a/src/sql/azdata.d.ts +++ b/src/sql/azdata.d.ts @@ -2132,7 +2132,7 @@ declare module 'azdata' { * AzureResource.ResourceManagement if not given) * @return Promise to return the security token */ - export function getSecurityToken(account: Account, resource?: AzureResource): Thenable<{}>; + export function getSecurityToken(account: Account, resource?: AzureResource): Thenable<{ [key: string]: any }>; /** * An [event](#Event) which fires when the accounts have changed.