From 806d807eaef2ef77b4b6bcd58ec6e137c1f4441c Mon Sep 17 00:00:00 2001 From: Kevin Cunnane Date: Wed, 23 Oct 2019 16:29:51 -0700 Subject: [PATCH] Refactor Azure Core extension for easier resource addition (#7958) Consolidated most logic into a base class and common resource request pattern. Reduces cost to add new providers, which will help for SQL Managed Instance support --- extensions/azurecore/package.json | 11 +- .../azurecore/src/azureResource/commands.ts | 22 +++- .../azurecore/src/azureResource/constants.ts | 5 +- .../azurecore/src/azureResource/interfaces.ts | 22 +++- .../providers/database/commands.ts | 52 -------- .../providers/database/databaseProvider.ts | 18 +-- .../providers/database/databaseService.ts | 9 +- .../database/databaseTreeDataProvider.ts | 112 +++++++----------- .../providers/database/interfaces.ts | 19 --- .../providers/database/models.ts | 13 -- .../providers/databaseServer/commands.ts | 52 -------- .../databaseServer/databaseServerProvider.ts | 19 +-- .../databaseServer/databaseServerService.ts | 9 +- .../databaseServerTreeDataProvider.ts | 111 +++++++---------- .../providers/databaseServer/interfaces.ts | 19 --- .../providers/databaseServer/models.ts | 13 -- .../providers/resourceTreeDataProviderBase.ts | 46 +++++++ extensions/azurecore/src/extension.ts | 11 +- .../database/databaseTreeDataProvider.test.ts | 11 +- .../databaseServerTreeDataProvider.test.ts | 11 +- 20 files changed, 200 insertions(+), 385 deletions(-) delete mode 100644 extensions/azurecore/src/azureResource/providers/database/commands.ts delete mode 100644 extensions/azurecore/src/azureResource/providers/database/interfaces.ts delete mode 100644 extensions/azurecore/src/azureResource/providers/database/models.ts delete mode 100644 extensions/azurecore/src/azureResource/providers/databaseServer/commands.ts delete mode 100644 extensions/azurecore/src/azureResource/providers/databaseServer/interfaces.ts delete mode 100644 extensions/azurecore/src/azureResource/providers/databaseServer/models.ts create mode 100644 extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index da0a6f07a6..c38845dfd7 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -138,10 +138,6 @@ { "command": "azure.resource.connectsqlserver", "when": "false" - }, - { - "command": "azure.resource.connectsqldb", - "when": "false" } ], "view/title": [ @@ -169,12 +165,7 @@ }, { "command": "azure.resource.connectsqlserver", - "when": "viewItem == azure.resource.itemType.databaseServer", - "group": "inline" - }, - { - "command": "azure.resource.connectsqldb", - "when": "viewItem == azure.resource.itemType.database", + "when": "viewItem == azure.resource.itemType.databaseServer || viewItem == azure.resource.itemType.database || viewItem == azure.resource.itemType.sqlInstance", "group": "inline" } ] diff --git a/extensions/azurecore/src/azureResource/commands.ts b/extensions/azurecore/src/azureResource/commands.ts index 0cded1421f..2782941e6d 100644 --- a/extensions/azurecore/src/azureResource/commands.ts +++ b/extensions/azurecore/src/azureResource/commands.ts @@ -3,10 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; import { window, QuickPickItem } from 'vscode'; -import { AzureResource } from 'azdata'; +import * as azdata from 'azdata'; import { TokenCredentials } from 'ms-rest'; import { AppContext } from '../appContext'; import * as nls from 'vscode-nls'; @@ -34,7 +33,7 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur const subscriptions = (await accountNode.getCachedSubscriptions()) || []; if (subscriptions.length === 0) { try { - const tokens = await this.servicePool.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement); + const tokens = await this.servicePool.apiWrapper.getSecurityToken(this.account, azdata.AzureResource.ResourceManagement); for (const tenant of this.account.properties.tenants) { const token = tokens[tenant.id].token; @@ -86,4 +85,21 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => { appContext.apiWrapper.executeCommand('workbench.actions.modal.linkedAccount'); }); + + appContext.apiWrapper.registerCommand('azure.resource.connectsqlserver', async (node?: TreeNode) => { + if (!node) { + return; + } + + const treeItem: azdata.TreeItem = await node.getTreeItem(); + if (!treeItem.payload) { + return; + } + // Ensure connection is saved to the Connections list, then open connection dialog + let connectionProfile = Object.assign({}, treeItem.payload, { saveProfile: true }); + const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true }); + if (conn) { + appContext.apiWrapper.executeCommand('workbench.view.connections'); + } + }); } diff --git a/extensions/azurecore/src/azureResource/constants.ts b/extensions/azurecore/src/azureResource/constants.ts index d12722302a..b7882700cc 100644 --- a/extensions/azurecore/src/azureResource/constants.ts +++ b/extensions/azurecore/src/azureResource/constants.ts @@ -3,8 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - export enum AzureResourceItemType { account = 'azure.resource.itemType.account', subscription = 'azure.resource.itemType.subscription', @@ -12,6 +10,7 @@ export enum AzureResourceItemType { database = 'azure.resource.itemType.database', databaseServerContainer = 'azure.resource.itemType.databaseServerContainer', databaseServer = 'azure.resource.itemType.databaseServer', + sqlInstance = 'azure.resource.itemType.sqlInstance', message = 'azure.resource.itemType.message' } @@ -22,4 +21,4 @@ export enum AzureResourceServiceNames { subscriptionService = 'AzureResourceSubscriptionService', subscriptionFilterService = 'AzureResourceSubscriptionFilterService', tenantService = 'AzureResourceTenantService' -} \ No newline at end of file +} diff --git a/extensions/azurecore/src/azureResource/interfaces.ts b/extensions/azurecore/src/azureResource/interfaces.ts index 30e3d8101b..b9cc4b5696 100644 --- a/extensions/azurecore/src/azureResource/interfaces.ts +++ b/extensions/azurecore/src/azureResource/interfaces.ts @@ -42,4 +42,24 @@ export interface IAzureResourceTenantService { export interface IAzureResourceNodeWithProviderId { resourceProviderId: string; resourceNode: azureResource.IAzureResourceNode; -} \ No newline at end of file +} + +export interface AzureSqlResource { + name: string; + loginName: string; +} + +export interface IAzureResourceService { + getResources(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise; +} + + +export interface AzureResourceDatabase extends AzureSqlResource { + serverName: string; + serverFullName: string; +} + +export interface AzureResourceDatabaseServer extends AzureSqlResource { + fullName: string; + defaultDatabaseName: string; +} diff --git a/extensions/azurecore/src/azureResource/providers/database/commands.ts b/extensions/azurecore/src/azureResource/providers/database/commands.ts deleted file mode 100644 index 954ba1b7d8..0000000000 --- a/extensions/azurecore/src/azureResource/providers/database/commands.ts +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { IConnectionProfile } from 'azdata'; -import { AppContext } from '../../../appContext'; - -import { TreeNode } from '../../treeNode'; -import { generateGuid } from '../../utils'; -import { AzureResourceItemType } from '../../constants'; -import { IAzureResourceDatabaseNode } from './interfaces'; -import { AzureResourceResourceTreeNode } from '../../resourceTreeNode'; - -export function registerAzureResourceDatabaseCommands(appContext: AppContext): void { - appContext.apiWrapper.registerCommand('azure.resource.connectsqldb', async (node?: TreeNode) => { - if (!node) { - return; - } - - const treeItem = await node.getTreeItem(); - if (treeItem.contextValue !== AzureResourceItemType.database) { - return; - } - - const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode; - const database = (resourceNode as IAzureResourceDatabaseNode).database; - - let connectionProfile: IConnectionProfile = { - id: generateGuid(), - connectionName: undefined, - serverName: database.serverFullName, - databaseName: database.name, - userName: database.loginName, - password: '', - authenticationType: 'SqlLogin', - savePassword: true, - groupFullName: '', - groupId: '', - providerName: 'MSSQL', - saveProfile: true, - options: {} - }; - - const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true }); - if (conn) { - appContext.apiWrapper.executeCommand('workbench.view.connections'); - } - }); -} \ No newline at end of file diff --git a/extensions/azurecore/src/azureResource/providers/database/databaseProvider.ts b/extensions/azurecore/src/azureResource/providers/database/databaseProvider.ts index 6ef0ccef6d..6fad11c92c 100644 --- a/extensions/azurecore/src/azureResource/providers/database/databaseProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/database/databaseProvider.ts @@ -3,24 +3,20 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; import { ExtensionContext } from 'vscode'; import { ApiWrapper } from '../../../apiWrapper'; import { azureResource } from '../../azure-resource'; -import { IAzureResourceDatabaseService } from './interfaces'; import { AzureResourceDatabaseTreeDataProvider } from './databaseTreeDataProvider'; +import { IAzureResourceService, AzureResourceDatabase } from '../../interfaces'; export class AzureResourceDatabaseProvider implements azureResource.IAzureResourceProvider { public constructor( - databaseService: IAzureResourceDatabaseService, - apiWrapper: ApiWrapper, - extensionContext: ExtensionContext + private _databaseService: IAzureResourceService, + private _apiWrapper: ApiWrapper, + private _extensionContext: ExtensionContext ) { - this._databaseService = databaseService; - this._apiWrapper = apiWrapper; - this._extensionContext = extensionContext; } public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider { @@ -30,8 +26,4 @@ export class AzureResourceDatabaseProvider implements azureResource.IAzureResour public get providerId(): string { return 'azure.resource.providers.database'; } - - private _databaseService: IAzureResourceDatabaseService = undefined; - private _apiWrapper: ApiWrapper = undefined; - private _extensionContext: ExtensionContext = undefined; -} \ No newline at end of file +} diff --git a/extensions/azurecore/src/azureResource/providers/database/databaseService.ts b/extensions/azurecore/src/azureResource/providers/database/databaseService.ts index af39c48ee8..9b9e0f1960 100644 --- a/extensions/azurecore/src/azureResource/providers/database/databaseService.ts +++ b/extensions/azurecore/src/azureResource/providers/database/databaseService.ts @@ -3,17 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import { ServiceClientCredentials } from 'ms-rest'; import { SqlManagementClient } from 'azure-arm-sql'; import { azureResource } from '../../azure-resource'; -import { IAzureResourceDatabaseService } from './interfaces'; -import { AzureResourceDatabase } from './models'; +import { IAzureResourceService, AzureResourceDatabase } from '../../interfaces'; -export class AzureResourceDatabaseService implements IAzureResourceDatabaseService { - public async getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise { +export class AzureResourceDatabaseService implements IAzureResourceService { + public async getResources(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise { const databases: AzureResourceDatabase[] = []; const sqlManagementClient = new SqlManagementClient(credential, subscription.id); const svrs = await sqlManagementClient.servers.list(); diff --git a/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts index 071d779369..4d3d5bae04 100644 --- a/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts @@ -3,82 +3,61 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { AzureResource, ExtensionNodeType } from 'azdata'; -import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode'; -import { TokenCredentials } from 'ms-rest'; +import { TreeItem, ExtensionNodeType } from 'azdata'; +import { TreeItemCollapsibleState, ExtensionContext } from 'vscode'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); import { azureResource } from '../../azure-resource'; -import { IAzureResourceDatabaseService, IAzureResourceDatabaseNode } from './interfaces'; -import { AzureResourceDatabase } from './models'; import { AzureResourceItemType } from '../../../azureResource/constants'; import { ApiWrapper } from '../../../apiWrapper'; import { generateGuid } from '../../utils'; +import { IAzureResourceService, AzureResourceDatabase } from '../../interfaces'; +import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase'; + +export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProviderBase { + + private static readonly containerId = 'azure.resource.providers.database.treeDataProvider.databaseContainer'; + private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', "SQL Databases"); -export class AzureResourceDatabaseTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider { public constructor( - databaseService: IAzureResourceDatabaseService, + databaseService: IAzureResourceService, apiWrapper: ApiWrapper, - extensionContext: ExtensionContext + private _extensionContext: ExtensionContext ) { - this._databaseService = databaseService; - this._apiWrapper = apiWrapper; - this._extensionContext = extensionContext; + super(databaseService, apiWrapper); + } + protected getTreeItemForResource(database: AzureResourceDatabase): TreeItem { + return { + id: `databaseServer_${database.serverFullName}.database_${database.name}`, + label: `${database.name} (${database.serverName})`, + iconPath: { + dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'), + light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg') + }, + collapsibleState: TreeItemCollapsibleState.Collapsed, + contextValue: AzureResourceItemType.database, + payload: { + id: generateGuid(), + connectionName: undefined, + serverName: database.serverFullName, + databaseName: database.name, + userName: database.loginName, + password: '', + authenticationType: 'SqlLogin', + savePassword: true, + groupFullName: '', + groupId: '', + providerName: 'MSSQL', + saveProfile: false, + options: {} + }, + childProvider: 'MSSQL', + type: ExtensionNodeType.Database + }; } - public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable { - return element.treeItem; - } - - public async getChildren(element?: azureResource.IAzureResourceNode): Promise { - if (!element) { - return [this.createContainerNode()]; - } - - const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement); - const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType); - - const databases: AzureResourceDatabase[] = (await this._databaseService.getDatabases(element.subscription, credential)) || []; - - return databases.map((database) => { - account: element.account, - subscription: element.subscription, - tenantId: element.tenantId, - database: database, - treeItem: { - id: `databaseServer_${database.serverFullName}.database_${database.name}`, - label: `${database.name} (${database.serverName})`, - iconPath: { - dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'), - light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg') - }, - collapsibleState: TreeItemCollapsibleState.Collapsed, - contextValue: AzureResourceItemType.database, - payload: { - id: generateGuid(), - connectionName: undefined, - serverName: database.serverFullName, - databaseName: database.name, - userName: database.loginName, - password: '', - authenticationType: 'SqlLogin', - savePassword: true, - groupFullName: '', - groupId: '', - providerName: 'MSSQL', - saveProfile: false, - options: {} - }, - childProvider: 'MSSQL', - type: ExtensionNodeType.Database - } - }); - } - - private createContainerNode(): azureResource.IAzureResourceNode { + protected createContainerNode(): azureResource.IAzureResourceNode { return { account: undefined, subscription: undefined, @@ -95,11 +74,4 @@ export class AzureResourceDatabaseTreeDataProvider implements azureResource.IAzu } }; } - - private _databaseService: IAzureResourceDatabaseService = undefined; - private _apiWrapper: ApiWrapper = undefined; - private _extensionContext: ExtensionContext = undefined; - - private static readonly containerId = 'azure.resource.providers.database.treeDataProvider.databaseContainer'; - private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', "SQL Databases"); } diff --git a/extensions/azurecore/src/azureResource/providers/database/interfaces.ts b/extensions/azurecore/src/azureResource/providers/database/interfaces.ts deleted file mode 100644 index 1c8acf384b..0000000000 --- a/extensions/azurecore/src/azureResource/providers/database/interfaces.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { ServiceClientCredentials } from 'ms-rest'; - -import { azureResource } from '../../azure-resource'; -import { AzureResourceDatabase } from './models'; - -export interface IAzureResourceDatabaseService { - getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise; -} - -export interface IAzureResourceDatabaseNode extends azureResource.IAzureResourceNode { - readonly database: AzureResourceDatabase; -} \ No newline at end of file diff --git a/extensions/azurecore/src/azureResource/providers/database/models.ts b/extensions/azurecore/src/azureResource/providers/database/models.ts deleted file mode 100644 index 2dab3f9b61..0000000000 --- a/extensions/azurecore/src/azureResource/providers/database/models.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -export interface AzureResourceDatabase { - name: string; - serverName: string; - serverFullName: string; - loginName: string; -} \ No newline at end of file diff --git a/extensions/azurecore/src/azureResource/providers/databaseServer/commands.ts b/extensions/azurecore/src/azureResource/providers/databaseServer/commands.ts deleted file mode 100644 index 664c205866..0000000000 --- a/extensions/azurecore/src/azureResource/providers/databaseServer/commands.ts +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { IConnectionProfile } from 'azdata'; -import { AppContext } from '../../../appContext'; - -import { TreeNode } from '../../treeNode'; -import { generateGuid } from '../../utils'; -import { AzureResourceItemType } from '../../constants'; -import { IAzureResourceDatabaseServerNode } from './interfaces'; -import { AzureResourceResourceTreeNode } from '../../resourceTreeNode'; - -export function registerAzureResourceDatabaseServerCommands(appContext: AppContext): void { - appContext.apiWrapper.registerCommand('azure.resource.connectsqlserver', async (node?: TreeNode) => { - if (!node) { - return; - } - - const treeItem = await node.getTreeItem(); - if (treeItem.contextValue !== AzureResourceItemType.databaseServer) { - return; - } - - const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode; - const databaseServer = (resourceNode as IAzureResourceDatabaseServerNode).databaseServer; - - let connectionProfile: IConnectionProfile = { - id: generateGuid(), - connectionName: undefined, - serverName: databaseServer.fullName, - databaseName: databaseServer.defaultDatabaseName, - userName: databaseServer.loginName, - password: '', - authenticationType: 'SqlLogin', - savePassword: true, - groupFullName: '', - groupId: '', - providerName: 'MSSQL', - saveProfile: true, - options: {} - }; - - const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true }); - if (conn) { - appContext.apiWrapper.executeCommand('workbench.view.connections'); - } - }); -} \ No newline at end of file diff --git a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerProvider.ts b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerProvider.ts index f2db96df3c..ad552763c8 100644 --- a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerProvider.ts @@ -3,24 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import { ExtensionContext } from 'vscode'; import { ApiWrapper } from '../../../apiWrapper'; import { azureResource } from '../../azure-resource'; -import { IAzureResourceDatabaseServerService } from './interfaces'; +import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces'; import { AzureResourceDatabaseServerTreeDataProvider } from './databaseServerTreeDataProvider'; export class AzureResourceDatabaseServerProvider implements azureResource.IAzureResourceProvider { public constructor( - databaseServerService: IAzureResourceDatabaseServerService, - apiWrapper: ApiWrapper, - extensionContext: ExtensionContext + private _databaseServerService: IAzureResourceService, + private _apiWrapper: ApiWrapper, + private _extensionContext: ExtensionContext ) { - this._databaseServerService = databaseServerService; - this._apiWrapper = apiWrapper; - this._extensionContext = extensionContext; } public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider { @@ -30,8 +25,4 @@ export class AzureResourceDatabaseServerProvider implements azureResource.IAzure public get providerId(): string { return 'azure.resource.providers.databaseServer'; } - - private _databaseServerService: IAzureResourceDatabaseServerService = undefined; - private _apiWrapper: ApiWrapper = undefined; - private _extensionContext: ExtensionContext = undefined; -} \ No newline at end of file +} diff --git a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerService.ts b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerService.ts index bf87ba9bbc..be12c12d18 100644 --- a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerService.ts +++ b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerService.ts @@ -3,17 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import { ServiceClientCredentials } from 'ms-rest'; import { SqlManagementClient } from 'azure-arm-sql'; import { azureResource } from '../../azure-resource'; -import { IAzureResourceDatabaseServerService } from './interfaces'; -import { AzureResourceDatabaseServer } from './models'; +import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces'; -export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService { - public async getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise { +export class AzureResourceDatabaseServerService implements IAzureResourceService { + public async getResources(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise { const databaseServers: AzureResourceDatabaseServer[] = []; const sqlManagementClient = new SqlManagementClient(credential, subscription.id); diff --git a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts index 5de167cdfa..f5eed277c6 100644 --- a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts @@ -3,82 +3,62 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { AzureResource, ExtensionNodeType } from 'azdata'; -import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode'; -import { TokenCredentials } from 'ms-rest'; +import { ExtensionNodeType, TreeItem } from 'azdata'; +import { TreeItemCollapsibleState, ExtensionContext } from 'vscode'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -import { azureResource } from '../../azure-resource'; -import { IAzureResourceDatabaseServerService, IAzureResourceDatabaseServerNode } from './interfaces'; -import { AzureResourceDatabaseServer } from './models'; import { AzureResourceItemType } from '../../../azureResource/constants'; import { ApiWrapper } from '../../../apiWrapper'; import { generateGuid } from '../../utils'; +import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces'; +import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase'; +import { azureResource } from '../../azure-resource'; + +export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDataProviderBase { + private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer'; + private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', "SQL Servers"); -export class AzureResourceDatabaseServerTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider { public constructor( - databaseServerService: IAzureResourceDatabaseServerService, + databaseServerService: IAzureResourceService, apiWrapper: ApiWrapper, - extensionContext: ExtensionContext + private _extensionContext: ExtensionContext ) { - this._databaseServerService = databaseServerService; - this._apiWrapper = apiWrapper; - this._extensionContext = extensionContext; + super(databaseServerService, apiWrapper); } - public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable { - return element.treeItem; + + protected getTreeItemForResource(databaseServer: AzureResourceDatabaseServer): TreeItem { + return { + id: `databaseServer_${databaseServer.name}`, + label: databaseServer.name, + iconPath: { + dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'), + light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg') + }, + collapsibleState: TreeItemCollapsibleState.Collapsed, + contextValue: AzureResourceItemType.databaseServer, + payload: { + id: generateGuid(), + connectionName: undefined, + serverName: databaseServer.fullName, + databaseName: databaseServer.defaultDatabaseName, + userName: databaseServer.loginName, + password: '', + authenticationType: 'SqlLogin', + savePassword: true, + groupFullName: '', + groupId: '', + providerName: 'MSSQL', + saveProfile: false, + options: {} + }, + childProvider: 'MSSQL', + type: ExtensionNodeType.Server + }; } - public async getChildren(element?: azureResource.IAzureResourceNode): Promise { - if (!element) { - return [this.createContainerNode()]; - } - - const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement); - const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType); - - const databaseServers: AzureResourceDatabaseServer[] = (await this._databaseServerService.getDatabaseServers(element.subscription, credential)) || []; - - return databaseServers.map((databaseServer) => { - account: element.account, - subscription: element.subscription, - tenantId: element.tenantId, - databaseServer: databaseServer, - treeItem: { - id: `databaseServer_${databaseServer.name}`, - label: databaseServer.name, - iconPath: { - dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'), - light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg') - }, - collapsibleState: TreeItemCollapsibleState.Collapsed, - contextValue: AzureResourceItemType.databaseServer, - payload: { - id: generateGuid(), - connectionName: undefined, - serverName: databaseServer.fullName, - databaseName: databaseServer.defaultDatabaseName, - userName: databaseServer.loginName, - password: '', - authenticationType: 'SqlLogin', - savePassword: true, - groupFullName: '', - groupId: '', - providerName: 'MSSQL', - saveProfile: false, - options: {} - }, - childProvider: 'MSSQL', - type: ExtensionNodeType.Server - } - }); - } - - private createContainerNode(): azureResource.IAzureResourceNode { + protected createContainerNode(): azureResource.IAzureResourceNode { return { account: undefined, subscription: undefined, @@ -95,11 +75,4 @@ export class AzureResourceDatabaseServerTreeDataProvider implements azureResourc } }; } - - private _databaseServerService: IAzureResourceDatabaseServerService = undefined; - private _apiWrapper: ApiWrapper = undefined; - private _extensionContext: ExtensionContext = undefined; - - private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer'; - private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', "SQL Servers"); } diff --git a/extensions/azurecore/src/azureResource/providers/databaseServer/interfaces.ts b/extensions/azurecore/src/azureResource/providers/databaseServer/interfaces.ts deleted file mode 100644 index b4cf6dc64f..0000000000 --- a/extensions/azurecore/src/azureResource/providers/databaseServer/interfaces.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { ServiceClientCredentials } from 'ms-rest'; - -import { azureResource } from '../../azure-resource'; -import { AzureResourceDatabaseServer } from './models'; - -export interface IAzureResourceDatabaseServerService { - getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credentials: ServiceClientCredentials): Promise; -} - -export interface IAzureResourceDatabaseServerNode extends azureResource.IAzureResourceNode { - readonly databaseServer: AzureResourceDatabaseServer; -} \ No newline at end of file diff --git a/extensions/azurecore/src/azureResource/providers/databaseServer/models.ts b/extensions/azurecore/src/azureResource/providers/databaseServer/models.ts deleted file mode 100644 index bf04e9acec..0000000000 --- a/extensions/azurecore/src/azureResource/providers/databaseServer/models.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -export interface AzureResourceDatabaseServer { - name: string; - fullName: string; - loginName: string; - defaultDatabaseName: string; -} \ No newline at end of file diff --git a/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts b/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts new file mode 100644 index 0000000000..3ab46b1d77 --- /dev/null +++ b/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureResource, TreeItem } from 'azdata'; +import { TokenCredentials } from 'ms-rest'; + +import { azureResource } from '../azure-resource'; +import { ApiWrapper } from '../../apiWrapper'; +import { IAzureResourceService, AzureSqlResource } from '../interfaces'; + +export abstract class ResourceTreeDataProviderBase implements azureResource.IAzureResourceTreeDataProvider { + + public constructor( + protected _resourceService: IAzureResourceService, + protected _apiWrapper: ApiWrapper, + ) { + } + + public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable { + return element.treeItem; + } + + public async getChildren(element?: azureResource.IAzureResourceNode): Promise { + if (!element) { + return [this.createContainerNode()]; + } + + const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement); + const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType); + + const resources: T[] = (await this._resourceService.getResources(element.subscription, credential)) || []; + + return resources.map((resource) => { + account: element.account, + subscription: element.subscription, + tenantId: element.tenantId, + treeItem: this.getTreeItemForResource(resource) + }); + } + + protected abstract getTreeItemForResource(resource: T): TreeItem; + + protected abstract createContainerNode(): azureResource.IAzureResourceNode; +} diff --git a/extensions/azurecore/src/extension.ts b/extensions/azurecore/src/extension.ts index feef12577a..5b1fd10bc0 100644 --- a/extensions/azurecore/src/extension.ts +++ b/extensions/azurecore/src/extension.ts @@ -26,8 +26,6 @@ import { AzureResourceSubscriptionFilterService } from './azureResource/services import { AzureResourceCacheService } from './azureResource/services/cacheService'; import { AzureResourceTenantService } from './azureResource/services/tenantService'; import { registerAzureResourceCommands } from './azureResource/commands'; -import { registerAzureResourceDatabaseServerCommands } from './azureResource/providers/databaseServer/commands'; -import { registerAzureResourceDatabaseCommands } from './azureResource/providers/database/commands'; import { AzureResourceTreeProvider } from './azureResource/tree/treeProvider'; let extensionContext: vscode.ExtensionContext; @@ -70,7 +68,7 @@ export async function activate(context: vscode.ExtensionContext) { registerAzureServices(appContext); const azureResourceTree = new AzureResourceTreeProvider(appContext); pushDisposable(apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree)); - registerCommands(appContext, azureResourceTree); + registerAzureResourceCommands(appContext, azureResourceTree); return { provideResources() { @@ -129,10 +127,3 @@ function registerAzureServices(appContext: AppContext): void { appContext.registerService(AzureResourceServiceNames.tenantService, new AzureResourceTenantService()); } -function registerCommands(appContext: AppContext, azureResourceTree: AzureResourceTreeProvider): void { - registerAzureResourceCommands(appContext, azureResourceTree); - - registerAzureResourceDatabaseServerCommands(appContext); - - registerAzureResourceDatabaseCommands(appContext); -} diff --git a/extensions/azurecore/src/test/azureResource/providers/database/databaseTreeDataProvider.test.ts b/extensions/azurecore/src/test/azureResource/providers/database/databaseTreeDataProvider.test.ts index 369694d14b..307f4b44af 100644 --- a/extensions/azurecore/src/test/azureResource/providers/database/databaseTreeDataProvider.test.ts +++ b/extensions/azurecore/src/test/azureResource/providers/database/databaseTreeDataProvider.test.ts @@ -11,13 +11,12 @@ import 'mocha'; import { azureResource } from '../../../../azureResource/azure-resource'; import { ApiWrapper } from '../../../../apiWrapper'; -import { IAzureResourceDatabaseService } from '../../../../azureResource/providers/database/interfaces'; import { AzureResourceDatabaseTreeDataProvider } from '../../../../azureResource/providers/database/databaseTreeDataProvider'; -import { AzureResourceDatabase } from '../../../../azureResource/providers/database/models'; import { AzureResourceItemType } from '../../../../azureResource/constants'; +import { IAzureResourceService, AzureResourceDatabase } from '../../../../azureResource/interfaces'; // Mock services -let mockDatabaseService: TypeMoq.IMock; +let mockDatabaseService: TypeMoq.IMock>; let mockApiWrapper: TypeMoq.IMock; let mockExtensionContext: TypeMoq.IMock; @@ -80,7 +79,7 @@ const mockDatabases: AzureResourceDatabase[] = [ describe('AzureResourceDatabaseTreeDataProvider.info', function (): void { beforeEach(() => { - mockDatabaseService = TypeMoq.Mock.ofType(); + mockDatabaseService = TypeMoq.Mock.ofType>(); mockApiWrapper = TypeMoq.Mock.ofType(); mockExtensionContext = TypeMoq.Mock.ofType(); }); @@ -98,12 +97,12 @@ describe('AzureResourceDatabaseTreeDataProvider.info', function (): void { describe('AzureResourceDatabaseTreeDataProvider.getChildren', function (): void { beforeEach(() => { - mockDatabaseService = TypeMoq.Mock.ofType(); + mockDatabaseService = TypeMoq.Mock.ofType>(); mockApiWrapper = TypeMoq.Mock.ofType(); mockExtensionContext = TypeMoq.Mock.ofType(); mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, azdata.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens)); - mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabases)); + mockDatabaseService.setup((o) => o.getResources(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabases)); mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString()); }); diff --git a/extensions/azurecore/src/test/azureResource/providers/databaseServer/databaseServerTreeDataProvider.test.ts b/extensions/azurecore/src/test/azureResource/providers/databaseServer/databaseServerTreeDataProvider.test.ts index 24426ce953..6610e57509 100644 --- a/extensions/azurecore/src/test/azureResource/providers/databaseServer/databaseServerTreeDataProvider.test.ts +++ b/extensions/azurecore/src/test/azureResource/providers/databaseServer/databaseServerTreeDataProvider.test.ts @@ -11,13 +11,12 @@ import 'mocha'; import { azureResource } from '../../../../azureResource/azure-resource'; import { ApiWrapper } from '../../../../apiWrapper'; -import { IAzureResourceDatabaseServerService } from '../../../../azureResource/providers/databaseServer/interfaces'; import { AzureResourceDatabaseServerTreeDataProvider } from '../../../../azureResource/providers/databaseServer/databaseServerTreeDataProvider'; -import { AzureResourceDatabaseServer } from '../../../../azureResource/providers/databaseServer/models'; import { AzureResourceItemType } from '../../../../azureResource/constants'; +import { IAzureResourceService, AzureResourceDatabaseServer } from '../../../../azureResource/interfaces'; // Mock services -let mockDatabaseServerService: TypeMoq.IMock; +let mockDatabaseServerService: TypeMoq.IMock>; let mockApiWrapper: TypeMoq.IMock; let mockExtensionContext: TypeMoq.IMock; @@ -80,7 +79,7 @@ const mockDatabaseServers: AzureResourceDatabaseServer[] = [ describe('AzureResourceDatabaseServerTreeDataProvider.info', function (): void { beforeEach(() => { - mockDatabaseServerService = TypeMoq.Mock.ofType(); + mockDatabaseServerService = TypeMoq.Mock.ofType>(); mockApiWrapper = TypeMoq.Mock.ofType(); mockExtensionContext = TypeMoq.Mock.ofType(); }); @@ -98,12 +97,12 @@ describe('AzureResourceDatabaseServerTreeDataProvider.info', function (): void { describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function (): void { beforeEach(() => { - mockDatabaseServerService = TypeMoq.Mock.ofType(); + mockDatabaseServerService = TypeMoq.Mock.ofType>(); mockApiWrapper = TypeMoq.Mock.ofType(); mockExtensionContext = TypeMoq.Mock.ofType(); mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, azdata.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens)); - mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabaseServers)); + mockDatabaseServerService.setup((o) => o.getResources(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabaseServers)); mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString()); });