diff --git a/extensions/azurecore/src/azureResource/constants.ts b/extensions/azurecore/src/azureResource/constants.ts index bbc52bf3e7..17693df748 100644 --- a/extensions/azurecore/src/azureResource/constants.ts +++ b/extensions/azurecore/src/azureResource/constants.ts @@ -24,7 +24,9 @@ export enum AzureResourceItemType { azureMonitor = 'azure.resource.itemType.azureMonitor', azureMonitorContainer = 'azure.resource.itemType.azureMonitorContainer', cosmosDBMongoAccount = 'azure.resource.itemType.cosmosDBMongoAccount', - cosmosDBMongoCluster = 'azure.resource.itemType.cosmosDBMongoCluster' + cosmosDBMongoCluster = 'azure.resource.itemType.cosmosDBMongoCluster', + cosmosDBPostgresAccount = 'azure.resource.itemType.cosmosDBPostgresAccount', + cosmosDBPostgresCluster = 'azure.resource.itemType.cosmosDBPostgresCluster' } export enum AzureResourceServiceNames { diff --git a/extensions/azurecore/src/azureResource/interfaces.ts b/extensions/azurecore/src/azureResource/interfaces.ts index 5e4df246f0..c47b9280ef 100644 --- a/extensions/azurecore/src/azureResource/interfaces.ts +++ b/extensions/azurecore/src/azureResource/interfaces.ts @@ -157,12 +157,6 @@ export interface IAzureResourceCacheService { update(key: string, value: T): Promise; } - -export interface IAzureResourceNodeWithProviderId { - resourceProviderId: string; - resourceNode: azureResource.IAzureResourceNode; -} - export interface IAzureResourceDbService extends azureResource.IAzureResourceService { convertDatabaseResource(resource: T, server?: S): azureResource.AzureResource | undefined; } diff --git a/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts index 522edf3e02..37fb8f9ae8 100644 --- a/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts @@ -56,13 +56,13 @@ export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase { - return [{ + public async getRootChild(): Promise { + return { id: AzureMonitorTreeDataProvider.containerId, label: AzureMonitorTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/logAnalyticsWorkspaces.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts index 35dd014959..d21f89228a 100644 --- a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts +++ b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts @@ -19,7 +19,7 @@ export class CosmosDbMongoService extends ResourceServiceBase public convertServerResource(resource: DbServerGraphData): AzureResourceMongoDatabaseServer | undefined { let host = resource.name; - const isServer = resource.type === azureResource.AzureResourceType.cosmosDbCluster; + const isServer = resource.type === azureResource.AzureResourceType.cosmosDbMongoCluster; if (isServer) { const url = new URL(resource.properties.connectionString); host = url.hostname; diff --git a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts index f6141cd08c..485550df59 100644 --- a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts @@ -29,7 +29,7 @@ export class CosmosDbMongoTreeDataProvider extends ResourceTreeDataProviderBase< public getTreeItemForResource(databaseServer: AzureResourceMongoDatabaseServer, account: azdata.Account): azdata.TreeItem { return { id: `${AzureResourcePrefixes.cosmosdb}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`, - label: `${databaseServer.name} (CosmosDB Mongo API)`, + label: this.browseConnectionMode ? `${databaseServer.name} (${CosmosDbMongoTreeDataProvider.CONTAINER_LABEL}, ${databaseServer.subscription.name})` : `${databaseServer.name}`, iconPath: this._extensionContext.asAbsolutePath('resources/cosmosDb.svg'), collapsibleState: TreeItemCollapsibleState.None, contextValue: AzureResourceItemType.cosmosDBMongoAccount, @@ -58,13 +58,13 @@ export class CosmosDbMongoTreeDataProvider extends ResourceTreeDataProviderBase< }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: CosmosDbMongoTreeDataProvider.CONTAINER_ID, label: CosmosDbMongoTreeDataProvider.CONTAINER_LABEL, iconPath: this._extensionContext.asAbsolutePath('resources/cosmosDb.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/cosmosdb/postgres/cosmosDbPostgresService.ts b/extensions/azurecore/src/azureResource/providers/cosmosdb/postgres/cosmosDbPostgresService.ts new file mode 100644 index 0000000000..8791661c92 --- /dev/null +++ b/extensions/azurecore/src/azureResource/providers/cosmosdb/postgres/cosmosDbPostgresService.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { ResourceServiceBase } from '../../resourceTreeDataProviderBase'; +import { azureResource } from 'azurecore'; +import { cosmosPostgresDbQuery } from '../../queryStringConstants'; +import { DbServerGraphData } from '../../../interfaces'; +import { COSMOSDB_POSTGRES_PROVIDER_ID } from '../../../../constants'; + +export interface AzureResourcePostgresDatabaseServer extends azureResource.AzureResourceDatabaseServer { + isServer: boolean; +} + +export class CosmosDbPostgresService extends ResourceServiceBase { + public override queryFilter: string = cosmosPostgresDbQuery; + + public convertServerResource(resource: DbServerGraphData): AzureResourcePostgresDatabaseServer | undefined { + let host = resource.name; + const isServer = resource.type === azureResource.AzureResourceType.cosmosDbPostgresCluster; + if (isServer) { + const url = new URL(resource.properties.connectionString); + host = url.hostname; + } + return { + id: resource.id, + name: resource.name, + provider: COSMOSDB_POSTGRES_PROVIDER_ID, + isServer: isServer, + fullName: host, + loginName: resource.properties.administratorLogin, + defaultDatabaseName: '', + tenant: resource.tenantId, + subscription: { + id: resource.subscriptionId, + name: resource.subscriptionName || '' + } + }; + } +} diff --git a/extensions/azurecore/src/azureResource/providers/cosmosdb/postgres/cosmosDbPostgresTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/cosmosdb/postgres/cosmosDbPostgresTreeDataProvider.ts new file mode 100644 index 0000000000..e294426cd7 --- /dev/null +++ b/extensions/azurecore/src/azureResource/providers/cosmosdb/postgres/cosmosDbPostgresTreeDataProvider.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TreeItemCollapsibleState, ExtensionContext } from 'vscode'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +import { AzureResourceItemType, AzureResourcePrefixes, pgsqlProvider } from '../../../constants'; +import { AzureResourcePostgresDatabaseServer } from './cosmosDbPostgresService'; +import { generateGuid } from '../../../utils'; +import { DbServerGraphData, GraphData } from '../../../interfaces'; +import { ResourceTreeDataProviderBase } from '../../resourceTreeDataProviderBase'; +import { AzureAccountProperties, azureResource } from 'azurecore'; +import * as azdata from 'azdata'; + +export class CosmosDbPostgresTreeDataProvider extends ResourceTreeDataProviderBase { + private static readonly CONTAINER_ID = 'azure.resource.providers.databaseServer.treeDataProvider.cosmosDbPostgresContainer'; + private static readonly CONTAINER_LABEL = localize('azure.resource.providers.databaseServer.treeDataProvider.cosmosDbPostgresContainerLabel', "Azure CosmosDB for PostgreSQL Cluster"); + + public constructor( + databaseServerService: azureResource.IAzureResourceService, + private _extensionContext: ExtensionContext + ) { + super(databaseServerService); + } + + public getTreeItemForResource(databaseServer: AzureResourcePostgresDatabaseServer, account: azdata.Account): azdata.TreeItem { + return { + id: `${AzureResourcePrefixes.cosmosdb}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`, + label: this.browseConnectionMode ? `${databaseServer.name} ${CosmosDbPostgresTreeDataProvider.CONTAINER_LABEL}, ${databaseServer.subscription.name})` : databaseServer.name, + iconPath: this._extensionContext.asAbsolutePath('resources/cosmosDb.svg'), + collapsibleState: TreeItemCollapsibleState.None, + contextValue: AzureResourceItemType.cosmosDBPostgresAccount, + payload: { + id: generateGuid(), + connectionName: databaseServer.name, + serverName: databaseServer.fullName, + userName: databaseServer.loginName, + password: '', + authenticationType: databaseServer.isServer ? azdata.connection.AuthenticationType.SqlLogin : azdata.connection.AuthenticationType.AzureMFA, + savePassword: true, + groupFullName: '', + groupId: '', + providerName: pgsqlProvider, + saveProfile: false, + options: { + isServer: databaseServer.isServer, + }, + azureAccount: account.key.accountId, + azureTenantId: databaseServer.tenant, + azureResourceId: databaseServer.id, + azurePortalEndpoint: (account.properties as AzureAccountProperties).providerSettings.settings.portalEndpoint + }, + childProvider: pgsqlProvider, + type: azdata.ExtensionNodeType.Server + }; + } + + public async getRootChild(): Promise { + return { + id: CosmosDbPostgresTreeDataProvider.CONTAINER_ID, + label: CosmosDbPostgresTreeDataProvider.CONTAINER_LABEL, + iconPath: this._extensionContext.asAbsolutePath('resources/cosmosDb.svg'), + collapsibleState: TreeItemCollapsibleState.Collapsed, + contextValue: AzureResourceItemType.databaseServerContainer + }; + } +} diff --git a/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts index 5b2b79ef68..7726f182ee 100644 --- a/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/database/databaseTreeDataProvider.ts @@ -57,13 +57,13 @@ export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProvi }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: AzureResourceDatabaseTreeDataProvider.containerId, label: AzureResourceDatabaseTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/sqlDatabase.svg'), collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts index 28c29a7d1f..c5d8774b25 100644 --- a/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/databaseServer/databaseServerTreeDataProvider.ts @@ -56,13 +56,13 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: AzureResourceDatabaseServerTreeDataProvider.containerId, label: AzureResourceDatabaseServerTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/sqlServer.svg'), collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/kusto/kustoTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/kusto/kustoTreeDataProvider.ts index ec963aaccc..8c8464e9b8 100644 --- a/extensions/azurecore/src/azureResource/providers/kusto/kustoTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/kusto/kustoTreeDataProvider.ts @@ -56,13 +56,13 @@ export class KustoTreeDataProvider extends ResourceTreeDataProviderBase { - return [{ + public async getRootChild(): Promise { + return { id: KustoTreeDataProvider.containerId, label: KustoTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/dataExplorerClusterDb.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/mysqlFlexibleServer/mysqlFlexibleServerTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/mysqlFlexibleServer/mysqlFlexibleServerTreeDataProvider.ts index 292dbb07d5..cbd75a6b39 100644 --- a/extensions/azurecore/src/azureResource/providers/mysqlFlexibleServer/mysqlFlexibleServerTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/mysqlFlexibleServer/mysqlFlexibleServerTreeDataProvider.ts @@ -57,13 +57,13 @@ export class MysqlFlexibleServerTreeDataProvider extends ResourceTreeDataProvide }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: MysqlFlexibleServerTreeDataProvider.CONTAINER_ID, label: MysqlFlexibleServerTreeDataProvider.CONTAINER_LABEL, iconPath: this._extensionContext.asAbsolutePath('resources/mysqlDatabase.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/postgresArcServer/postgresArcServerTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/postgresArcServer/postgresArcServerTreeDataProvider.ts index 99a7a3debe..cce950dd66 100644 --- a/extensions/azurecore/src/azureResource/providers/postgresArcServer/postgresArcServerTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/postgresArcServer/postgresArcServerTreeDataProvider.ts @@ -60,13 +60,13 @@ export class PostgresServerArcTreeDataProvider extends ResourceTreeDataProviderB }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: PostgresServerArcTreeDataProvider.containerId, label: PostgresServerArcTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/azureArcPostgresServer.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/postgresFlexibleServer/postgresFlexibleServerTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/postgresFlexibleServer/postgresFlexibleServerTreeDataProvider.ts index 88f183d1b2..cbe963d2b2 100644 --- a/extensions/azurecore/src/azureResource/providers/postgresFlexibleServer/postgresFlexibleServerTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/postgresFlexibleServer/postgresFlexibleServerTreeDataProvider.ts @@ -59,13 +59,13 @@ export class PostgresFlexibleServerTreeDataProvider extends ResourceTreeDataProv }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: PostgresFlexibleServerTreeDataProvider.containerId, label: PostgresFlexibleServerTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/postgresServer/postgresServerTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/postgresServer/postgresServerTreeDataProvider.ts index baa4f77957..c114ad30d8 100644 --- a/extensions/azurecore/src/azureResource/providers/postgresServer/postgresServerTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/postgresServer/postgresServerTreeDataProvider.ts @@ -16,7 +16,7 @@ import { AzureAccount, azureResource } from 'azurecore'; export class PostgresServerTreeDataProvider extends ResourceTreeDataProviderBase { private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.postgresServerContainer'; - private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.postgresServerContainerLabel', "Azure Database for PostgreSQL servers"); + private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.postgresServerContainerLabel', "Azure Database for PostgreSQL Servers"); public constructor( databaseServerService: azureResource.IAzureResourceService, @@ -29,10 +29,7 @@ export class PostgresServerTreeDataProvider extends ResourceTreeDataProviderBase return { id: `${AzureResourcePrefixes.postgresServer}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`, label: this.browseConnectionMode ? `${databaseServer.name} (${PostgresServerTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name, - iconPath: { - dark: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'), - light: this._extensionContext.asAbsolutePath('resources/postgresServer.svg') - }, + iconPath: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'), collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServer, payload: { @@ -62,16 +59,13 @@ export class PostgresServerTreeDataProvider extends ResourceTreeDataProviderBase }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: PostgresServerTreeDataProvider.containerId, label: PostgresServerTreeDataProvider.containerLabel, - iconPath: { - dark: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'), - light: this._extensionContext.asAbsolutePath('resources/postgresServer.svg') - }, + iconPath: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts b/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts index 514f282a53..db5fd048dd 100644 --- a/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts +++ b/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts @@ -46,7 +46,7 @@ export const resourceGroupQuery = `ResourceContainers | where type=="${azureReso /** * Lists all postgreSQL servers */ -export const postgresServerQuery = `type == "${azureResource.AzureResourceType.postgresServer}"`; +export const postgresServerQuery = `type == "${azureResource.AzureResourceType.postgresServer}" or type == "${azureResource.AzureResourceType.postgresServerv2}" or type == "${azureResource.AzureResourceType.postgresSingleServer}"`; /** * Lists all postgreSQL flexible servers @@ -71,7 +71,12 @@ export const kustoClusterQuery = `type == "${azureResource.AzureResourceType.kus /** * Lists all Cosmos DB for MongoDB accounts */ -export const cosmosMongoDbQuery = `(type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "${Constants.mongoDbKind}") or type == "${azureResource.AzureResourceType.cosmosDbCluster}"`; +export const cosmosMongoDbQuery = `(type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "${Constants.mongoDbKind}") or type == "${azureResource.AzureResourceType.cosmosDbMongoCluster}"`; + +/** + * Lists all Cosmos DB for MongoDB accounts + */ +export const cosmosPostgresDbQuery = `type == "${azureResource.AzureResourceType.postgresServerGroup}" or type == "${azureResource.AzureResourceType.postgresServerGroupv2}"`; /** * Lists all Log Analytics workspaces diff --git a/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts b/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts index 786502b1bb..f67d7a76ca 100644 --- a/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts +++ b/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts @@ -56,7 +56,7 @@ export abstract class ResourceTreeDataProviderBase; + public abstract getRootChild(): Promise; } export async function queryGraphResources(resourceClient: ResourceGraphClient, subscriptions: azureResource.AzureResourceSubscription[], resourceQuery: string): Promise { diff --git a/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceTreeDataProvider.ts index 2090f4579e..1d5493868d 100644 --- a/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceTreeDataProvider.ts @@ -56,13 +56,13 @@ export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase { - return [{ + public async getRootChild(): Promise { + return { id: SqlInstanceTreeDataProvider.containerId, label: SqlInstanceTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/sqlManagedInstance.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/sqlinstanceArc/sqlInstanceArcTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/sqlinstanceArc/sqlInstanceArcTreeDataProvider.ts index d7706f760a..db6c64f869 100644 --- a/extensions/azurecore/src/azureResource/providers/sqlinstanceArc/sqlInstanceArcTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/sqlinstanceArc/sqlInstanceArcTreeDataProvider.ts @@ -57,13 +57,13 @@ export class SqlInstanceArcTreeDataProvider extends ResourceTreeDataProviderBase }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: SqlInstanceArcTreeDataProvider.containerId, label: SqlInstanceArcTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/azureArcSqlManagedInstance.svg'), collapsibleState: TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.databaseServerContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/synapseSqlPool/synapseSqlPoolTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/synapseSqlPool/synapseSqlPoolTreeDataProvider.ts index 3cfb6164bf..8fbc99bb57 100644 --- a/extensions/azurecore/src/azureResource/providers/synapseSqlPool/synapseSqlPoolTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/synapseSqlPool/synapseSqlPoolTreeDataProvider.ts @@ -57,13 +57,13 @@ export class AzureResourceSynapseSqlPoolTreeDataProvider extends ResourceTreeDat }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: AzureResourceSynapseSqlPoolTreeDataProvider.containerId, label: AzureResourceSynapseSqlPoolTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/sqlPools.svg'), collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.synapseSqlPoolContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/synapseWorkspace/synapseWorkspaceTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/synapseWorkspace/synapseWorkspaceTreeDataProvider.ts index f6b4443bca..772e794ced 100644 --- a/extensions/azurecore/src/azureResource/providers/synapseWorkspace/synapseWorkspaceTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/synapseWorkspace/synapseWorkspaceTreeDataProvider.ts @@ -56,13 +56,13 @@ export class AzureResourceSynapseWorkspaceTreeDataProvider extends ResourceTreeD }; } - public async getRootChildren(): Promise { - return [{ + public async getRootChild(): Promise { + return { id: AzureResourceSynapseWorkspaceTreeDataProvider.containerId, label: AzureResourceSynapseWorkspaceTreeDataProvider.containerLabel, iconPath: this._extensionContext.asAbsolutePath('resources/azureSynapseAnalytics.svg'), collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, contextValue: AzureResourceItemType.synapseWorkspaceContainer - }]; + }; } } diff --git a/extensions/azurecore/src/azureResource/providers/universal/universalService.ts b/extensions/azurecore/src/azureResource/providers/universal/universalService.ts index bfaf41cd31..76af8dd3b1 100644 --- a/extensions/azurecore/src/azureResource/providers/universal/universalService.ts +++ b/extensions/azurecore/src/azureResource/providers/universal/universalService.ts @@ -13,8 +13,8 @@ import * as nls from 'vscode-nls'; import { AzureResourcePrefixes, ResourceCategory, analyticsKind, mongoDbKind } from '../../constants'; import { COSMOSDB_MONGO_PROVIDER_ID, DATABASE_PROVIDER_ID, DATABASE_SERVER_PROVIDER_ID, KUSTO_PROVIDER_ID, AZURE_MONITOR_PROVIDER_ID, - MYSQL_FLEXIBLE_SERVER_PROVIDER_ID, POSTGRES_FLEXIBLE_SERVER_PROVIDER_ID, POSTGRES_SERVER_PROVIDER_ID, POSTGRES_ARC_SERVER_PROVIDER_ID, - SQLINSTANCE_PROVIDER_ID, SQLINSTANCE_ARC_PROVIDER_ID, SYNAPSE_SQL_POOL_PROVIDER_ID, SYNAPSE_WORKSPACE_PROVIDER_ID + MYSQL_FLEXIBLE_SERVER_PROVIDER_ID, POSTGRES_SERVER_PROVIDER_ID, POSTGRES_ARC_SERVER_PROVIDER_ID, + SQLINSTANCE_PROVIDER_ID, SQLINSTANCE_ARC_PROVIDER_ID, SYNAPSE_SQL_POOL_PROVIDER_ID, SYNAPSE_WORKSPACE_PROVIDER_ID, COSMOSDB_POSTGRES_PROVIDER_ID, POSTGRES_FLEXIBLE_SERVER_PROVIDER_ID } from '../../../constants'; import { Logger } from '../../../utils/Logger'; @@ -79,8 +79,10 @@ export class AzureResourceUniversalService implements azureResource.IAzureResour public getProviderFromResourceType(type: string, kind?: string): [provider: azureResource.IAzureResourceTreeDataProvider, category: ResourceCategory] { - if ((type === azureResource.AzureResourceType.cosmosDbAccount && kind === mongoDbKind) || type === azureResource.AzureResourceType.cosmosDbCluster) { + if ((type === azureResource.AzureResourceType.cosmosDbAccount && kind === mongoDbKind) || type === azureResource.AzureResourceType.cosmosDbMongoCluster) { return [this.getRegisteredTreeDataProviderInstance(COSMOSDB_MONGO_PROVIDER_ID), ResourceCategory.Server]; + } else if (type === azureResource.AzureResourceType.postgresServerGroup || type === azureResource.AzureResourceType.postgresServerGroupv2) { + return [this.getRegisteredTreeDataProviderInstance(COSMOSDB_POSTGRES_PROVIDER_ID), ResourceCategory.Server]; } else if (type === azureResource.AzureResourceType.sqlDatabase || type === azureResource.AzureResourceType.sqlSynapseSqlDatabase) { return [this.getRegisteredTreeDataProviderInstance(DATABASE_PROVIDER_ID), ResourceCategory.Database]; } else if (type === azureResource.AzureResourceType.sqlServer && kind !== analyticsKind) { @@ -91,10 +93,10 @@ export class AzureResourceUniversalService implements azureResource.IAzureResour return [this.getRegisteredTreeDataProviderInstance(AZURE_MONITOR_PROVIDER_ID), ResourceCategory.Server]; } else if (type === azureResource.AzureResourceType.mysqlFlexibleServer) { return [this.getRegisteredTreeDataProviderInstance(MYSQL_FLEXIBLE_SERVER_PROVIDER_ID), ResourceCategory.Server]; + } else if (type === azureResource.AzureResourceType.postgresServer || type === azureResource.AzureResourceType.postgresServerv2 || type === azureResource.AzureResourceType.postgresSingleServer) { + return [this.getRegisteredTreeDataProviderInstance(POSTGRES_SERVER_PROVIDER_ID), ResourceCategory.Server]; } else if (type === azureResource.AzureResourceType.postgresFlexibleServer) { return [this.getRegisteredTreeDataProviderInstance(POSTGRES_FLEXIBLE_SERVER_PROVIDER_ID), ResourceCategory.Server]; - } else if (type === azureResource.AzureResourceType.postgresServer) { - return [this.getRegisteredTreeDataProviderInstance(POSTGRES_SERVER_PROVIDER_ID), ResourceCategory.Server]; } else if (type === azureResource.AzureResourceType.azureArcPostgresServer) { return [this.getRegisteredTreeDataProviderInstance(POSTGRES_ARC_SERVER_PROVIDER_ID), ResourceCategory.Server]; } else if (type === azureResource.AzureResourceType.sqlManagedInstance) { @@ -123,10 +125,10 @@ export class AzureResourceUniversalService implements azureResource.IAzureResour return this.getRegisteredTreeDataProviderInstance(AZURE_MONITOR_PROVIDER_ID); } else if (id.startsWith(AzureResourcePrefixes.mySqlFlexibleServer)) { return this.getRegisteredTreeDataProviderInstance(MYSQL_FLEXIBLE_SERVER_PROVIDER_ID); - } else if (id.startsWith(AzureResourcePrefixes.postgresFlexibleServer)) { - return this.getRegisteredTreeDataProviderInstance(POSTGRES_FLEXIBLE_SERVER_PROVIDER_ID); } else if (id.startsWith(AzureResourcePrefixes.postgresServer)) { return this.getRegisteredTreeDataProviderInstance(POSTGRES_SERVER_PROVIDER_ID); + } else if (id.startsWith(AzureResourcePrefixes.postgresFlexibleServer)) { + return this.getRegisteredTreeDataProviderInstance(POSTGRES_FLEXIBLE_SERVER_PROVIDER_ID); } else if (id.startsWith(AzureResourcePrefixes.postgresServerArc)) { return this.getRegisteredTreeDataProviderInstance(POSTGRES_ARC_SERVER_PROVIDER_ID); } else if (id.startsWith(AzureResourcePrefixes.sqlInstance)) { diff --git a/extensions/azurecore/src/azureResource/providers/universal/universalTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/universal/universalTreeDataProvider.ts index 7b22d47001..bf494dabaf 100644 --- a/extensions/azurecore/src/azureResource/providers/universal/universalTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/universal/universalTreeDataProvider.ts @@ -37,7 +37,7 @@ export class AzureResourceUniversalTreeDataProvider { + public async getRootChild(): Promise { throw new Error('Method not supported'); } @@ -48,6 +48,7 @@ export class AzureResourceUniversalTreeDataProvider (a.treeItem.label).localeCompare(b.treeItem.label)); } catch (error) { diff --git a/extensions/azurecore/src/azureResource/resourceService.ts b/extensions/azurecore/src/azureResource/resourceService.ts index e5584439bf..542eb65059 100644 --- a/extensions/azurecore/src/azureResource/resourceService.ts +++ b/extensions/azurecore/src/azureResource/resourceService.ts @@ -6,7 +6,6 @@ import { extensions } from 'vscode'; import * as azdata from 'azdata'; -import { IAzureResourceNodeWithProviderId } from './interfaces'; import { AzureAccount, azureResource } from 'azurecore'; import { UNIVERSAL_PROVIDER_ID } from '../constants'; @@ -33,34 +32,34 @@ export class AzureResourceService { this.doRegisterResourceProvider(resourceProvider); } + public registerUniversalResourceProvider(resourceProvider: azureResource.IAzureUniversalResourceProvider): void { + this._universalProvider = resourceProvider; + } + public clearResourceProviders(): void { this._resourceProviders = {}; this._treeDataProviders = {}; this._areResourceProvidersLoaded = false; } - public async getRootChildren(resourceProviderId: string, account: AzureAccount, subscription: azureResource.AzureResourceSubscription): Promise { + public async getRootChild(resourceProviderId: string, account: AzureAccount, subscription: azureResource.AzureResourceSubscription): Promise { await this.ensureResourceProvidersRegistered(); if (!(resourceProviderId in this._resourceProviders) && resourceProviderId !== UNIVERSAL_PROVIDER_ID) { throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`); } - const rootChildren = await this._treeDataProviders[resourceProviderId]?.getRootChildren(); - return rootChildren.map(rootChild => { - return { - resourceProviderId, - resourceNode: { - account, - subscription, - tenantId: subscription.tenant!, - treeItem: rootChild - } - }; - }); + const rootChild = await this._treeDataProviders[resourceProviderId]?.getRootChild(); + return { + account: account, + subscription: subscription, + tenantId: subscription.tenant!, + resourceProviderId: resourceProviderId, + treeItem: rootChild + }; } - public async getChildren(resourceProviderId: string, element: azureResource.IAzureResourceNode, browseConnectionMode: boolean = false): Promise { + public async getChildren(resourceProviderId: string, element: azureResource.IAzureResourceNode, browseConnectionMode: boolean = false): Promise { await this.ensureResourceProvidersRegistered(); if (!(resourceProviderId in this._resourceProviders) && resourceProviderId !== UNIVERSAL_PROVIDER_ID) { @@ -70,23 +69,15 @@ export class AzureResourceService { const treeDataProvider = this._treeDataProviders[resourceProviderId]; treeDataProvider.browseConnectionMode = browseConnectionMode; const children = await treeDataProvider.getChildren(element); - - return children.map((child) => { - resourceProviderId: resourceProviderId, - resourceNode: child - }); + return children; } - public async getAllChildren(account: AzureAccount, subscriptions: azureResource.AzureResourceSubscription[], browseConnectionMode: boolean = false): Promise { + public async getAllChildren(account: AzureAccount, subscriptions: azureResource.AzureResourceSubscription[], browseConnectionMode: boolean = false): Promise { await this.ensureResourceProvidersRegistered(); const treeDataProvider = this._universalProvider?.getTreeDataProvider(); treeDataProvider.browseConnectionMode = browseConnectionMode; const children = await treeDataProvider.getAllChildren(account, subscriptions); - - return children.map((child) => { - resourceProviderId: UNIVERSAL_PROVIDER_ID, - resourceNode: child - }); + return children; } public get areResourceProvidersLoaded(): boolean { diff --git a/extensions/azurecore/src/azureResource/resourceTreeNode.ts b/extensions/azurecore/src/azureResource/resourceTreeNode.ts index 85ccfca8d8..05549b4cc8 100644 --- a/extensions/azurecore/src/azureResource/resourceTreeNode.ts +++ b/extensions/azurecore/src/azureResource/resourceTreeNode.ts @@ -10,17 +10,17 @@ const localize = nls.loadMessageBundle(); import { TreeNode } from './treeNode'; import { AzureResourceService } from './resourceService'; -import { IAzureResourceNodeWithProviderId } from './interfaces'; import { AzureResourceMessageTreeNode } from './messageTreeNode'; import { AzureResourceErrorMessageUtil } from './utils'; import { AppContext } from '../appContext'; import { AzureResourceServiceNames } from './constants'; +import { azureResource } from 'azurecore'; export class AzureResourceResourceTreeNode extends TreeNode { private _resourceService: AzureResourceService; public constructor( - public readonly resourceNodeWithProviderId: IAzureResourceNodeWithProviderId, + public readonly resourceNode: azureResource.IAzureResourceNode, parent: TreeNode, private appContext: AppContext ) { @@ -31,19 +31,19 @@ export class AzureResourceResourceTreeNode extends TreeNode { public async getChildren(): Promise { // It is a leaf node. - if (this.resourceNodeWithProviderId.resourceNode.treeItem.collapsibleState === TreeItemCollapsibleState.None) { + if (this.resourceNode.treeItem.collapsibleState === TreeItemCollapsibleState.None) { return []; } try { - const children = await this._resourceService.getChildren(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode); + const children = await this._resourceService.getChildren(this.resourceNode.resourceProviderId, this.resourceNode); if (children.length === 0) { return [AzureResourceMessageTreeNode.create(localize('azure.resource.resourceTreeNode.noResourcesLabel', "No Resources found"), this)]; } else { return children.map((child) => { // To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered' - child.resourceNode.treeItem.id = `${this.resourceNodeWithProviderId.resourceNode.treeItem.id}.${child.resourceNode.treeItem.id}`; + child.treeItem.id = `${this.resourceNode.treeItem.id}.${child.treeItem.id}`; return new AzureResourceResourceTreeNode(child, this, this.appContext); }); } @@ -53,11 +53,11 @@ export class AzureResourceResourceTreeNode extends TreeNode { } public getTreeItem(): TreeItem | Promise { - return this.resourceNodeWithProviderId.resourceNode.treeItem; + return this.resourceNode.treeItem; } public getNodeInfo(): NodeInfo { - const treeItem = this.resourceNodeWithProviderId.resourceNode.treeItem; + const treeItem = this.resourceNode.treeItem; return { label: typeof treeItem.label === 'object' ? treeItem.label.label : treeItem.label || '', @@ -74,7 +74,7 @@ export class AzureResourceResourceTreeNode extends TreeNode { } public get nodePathValue(): string { - return this.resourceNodeWithProviderId.resourceNode.treeItem.id || ''; + return this.resourceNode.treeItem.id || ''; } } diff --git a/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts b/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts index c3ed4e6624..8fd03b7815 100644 --- a/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts +++ b/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts @@ -10,7 +10,6 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); import { TreeNode } from '../treeNode'; -import { IAzureResourceNodeWithProviderId } from '../interfaces'; import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes'; import { AzureResourceItemType, AzureResourceServiceNames } from '../constants'; import { IAzureResourceTreeChangeHandler } from './treeChangeHandler'; @@ -38,19 +37,21 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTre public async getChildren(): Promise { try { const resourceService = this.appContext.getService(AzureResourceServiceNames.resourceService); - - const children: IAzureResourceNodeWithProviderId[] = []; - - for (const resourceProviderId of await resourceService.listResourceProviderIds()) { - children.push(...await resourceService.getRootChildren(resourceProviderId, this.account, this.subscription)); - } - + const children: azureResource.IAzureResourceNode[] = await resourceService.getAllChildren(this.account, [this.subscription], true); + let resourceTreeNodes: azureResource.IAzureResourceNode[] = []; if (children.length === 0) { return [AzureResourceMessageTreeNode.create(AzureResourceSubscriptionTreeNode.noResourcesLabel, this)]; } else { - return children.map((child) => { + for (let resource of children) { + if (resourceTreeNodes.findIndex(r => r.resourceProviderId === resource.resourceProviderId) !== -1) { + continue; + } else { + resourceTreeNodes.push(await resourceService.getRootChild(resource.resourceProviderId, this.account, this.subscription)); + } + } + return resourceTreeNodes.map((child) => { // To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered' - child.resourceNode.treeItem.id = `${this._id}.${child.resourceNode.treeItem.id}`; + child.treeItem.id = `${this._id}.${child.treeItem.id}`; return new AzureResourceResourceTreeNode(child, this, this.appContext); }).sort((a, b) => a.nodePathValue.localeCompare(b.nodePathValue)); } diff --git a/extensions/azurecore/src/azureResource/utils.ts b/extensions/azurecore/src/azureResource/utils.ts index 2712650d6b..5b524f849a 100644 --- a/extensions/azurecore/src/azureResource/utils.ts +++ b/extensions/azurecore/src/azureResource/utils.ts @@ -50,6 +50,8 @@ import { AzureResourceSynapseWorkspaceService } from './providers/synapseWorkspa import { AzureResourceSynapseWorkspaceTreeDataProvider } from './providers/synapseWorkspace/synapseWorkspaceTreeDataProvider'; import { PostgresFlexibleServerTreeDataProvider } from './providers/postgresFlexibleServer/postgresFlexibleServerTreeDataProvider'; import { PostgresFlexibleServerService } from './providers/postgresFlexibleServer/postgresFlexibleServerService'; +import { CosmosDbPostgresTreeDataProvider } from './providers/cosmosdb/postgres/cosmosDbPostgresTreeDataProvider'; +import { CosmosDbPostgresService } from './providers/cosmosdb/postgres/cosmosDbPostgresService'; const localize = nls.loadMessageBundle(); @@ -266,6 +268,7 @@ export function getAllResourceProviders(extensionContext: vscode.ExtensionContex const providers: azureResource.IAzureResourceProvider[] = [ new ResourceProvider(Constants.AZURE_MONITOR_PROVIDER_ID, new AzureMonitorTreeDataProvider(new AzureMonitorResourceService(), extensionContext)), new ResourceProvider(Constants.COSMOSDB_MONGO_PROVIDER_ID, new CosmosDbMongoTreeDataProvider(new CosmosDbMongoService(), extensionContext)), + new ResourceProvider(Constants.COSMOSDB_POSTGRES_PROVIDER_ID, new CosmosDbPostgresTreeDataProvider(new CosmosDbPostgresService(), extensionContext)), new ResourceProvider(Constants.DATABASE_PROVIDER_ID, new AzureResourceDatabaseTreeDataProvider(new AzureResourceDatabaseService(), extensionContext)), new ResourceProvider(Constants.DATABASE_SERVER_PROVIDER_ID, new AzureResourceDatabaseServerTreeDataProvider(new AzureResourceDatabaseServerService(), extensionContext)), new ResourceProvider(Constants.KUSTO_PROVIDER_ID, new KustoTreeDataProvider(new KustoResourceService(), extensionContext)), diff --git a/extensions/azurecore/src/azurecore.d.ts b/extensions/azurecore/src/azurecore.d.ts index 6712112b95..c41af3bc11 100644 --- a/extensions/azurecore/src/azurecore.d.ts +++ b/extensions/azurecore/src/azurecore.d.ts @@ -378,12 +378,17 @@ declare module 'azurecore' { kustoClusters = 'microsoft.kusto/clusters', azureArcPostgresServer = 'microsoft.azuredata/postgresinstances', postgresServer = 'microsoft.dbforpostgresql/servers', + postgresServerv2 = 'microsoft.dbforpostgresql/serversv2', + postgresSingleServer = 'microsoft.dbforpostgresql/singleservers', postgresFlexibleServer = 'microsoft.dbforpostgresql/flexibleservers', + postgresServerGroup = 'microsoft.dbforpostgresql/servergroups', + postgresServerGroupv2 = 'microsoft.dbforpostgresql/servergroupsv2', azureArcService = 'microsoft.azuredata/datacontrollers', storageAccount = 'microsoft.storage/storageaccounts', logAnalytics = 'microsoft.operationalinsights/workspaces', cosmosDbAccount = 'microsoft.documentdb/databaseaccounts', - cosmosDbCluster = 'microsoft.documentdb/mongoclusters', + cosmosDbPostgresCluster = 'microsoft.documentdb/postgresclusters', + cosmosDbMongoCluster = 'microsoft.documentdb/mongoclusters', mysqlFlexibleServer = 'microsoft.dbformysql/flexibleservers' } @@ -406,9 +411,9 @@ declare module 'azurecore' { getService(): azureResource.IAzureResourceService; /** * Gets the root tree item nodes for this provider - these will be used as - * direct children of the Account node in the Azure tree view. + * direct children of the Tenant node in the Azure tree view. */ - getRootChildren(): Promise; + getRootChild(): Promise; /** * Gets the children for a given {@link IAzureResourceNode} * @param element The parent node to get the children for @@ -427,6 +432,7 @@ declare module 'azurecore' { readonly account: AzureAccount; readonly subscription: AzureResourceSubscription; readonly tenantId: string; + readonly resourceProviderId: string; readonly treeItem: azdata.TreeItem; } diff --git a/extensions/azurecore/src/constants.ts b/extensions/azurecore/src/constants.ts index 51bf8d4852..3ecd10b872 100644 --- a/extensions/azurecore/src/constants.ts +++ b/extensions/azurecore/src/constants.ts @@ -146,6 +146,7 @@ export enum Platform { /////////////// Azure Resource provider Ids export const AZURE_MONITOR_PROVIDER_ID = 'azure.resource.providers.azureMonitor'; export const COSMOSDB_MONGO_PROVIDER_ID = 'azure.resource.providers.cosmosDbMongo'; +export const COSMOSDB_POSTGRES_PROVIDER_ID = 'azure.resource.providers.cosmosDbPostgres'; export const DATABASE_PROVIDER_ID = 'azure.resource.providers.database'; export const DATABASE_SERVER_PROVIDER_ID = 'azure.resource.providers.databaseServer'; export const KUSTO_PROVIDER_ID = 'azure.resource.providers.azureDataExplorer'; 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 06980ca57c..7e75ba855b 100644 --- a/extensions/azurecore/src/test/azureResource/providers/database/databaseTreeDataProvider.test.ts +++ b/extensions/azurecore/src/test/azureResource/providers/database/databaseTreeDataProvider.test.ts @@ -58,6 +58,7 @@ const mockResourceRootNode: azureResource.IAzureResourceNode = { account: mockAccount, subscription: mockSubscription, tenantId: mockTenantId, + resourceProviderId: 'mock_resource_provider', treeItem: { id: 'mock_resource_root_node', label: 'mock resource root node', @@ -135,12 +136,9 @@ describe('AzureResourceDatabaseTreeDataProvider.getChildren', function (): void it('Should return container node when element is undefined.', async function (): Promise { const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockExtensionContext.object); - const children = await treeDataProvider.getRootChildren(); + const child = await treeDataProvider.getRootChild(); - should(children).Array(); - should(children.length).equal(1); - - const child = children[0]; + should(child).Object(); should(child.id).equal('azure.resource.providers.database.treeDataProvider.databaseContainer'); should(child.label).equal('SQL databases'); should(child.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed); 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 f99106a1c4..001596c0de 100644 --- a/extensions/azurecore/src/test/azureResource/providers/databaseServer/databaseServerTreeDataProvider.test.ts +++ b/extensions/azurecore/src/test/azureResource/providers/databaseServer/databaseServerTreeDataProvider.test.ts @@ -57,6 +57,7 @@ const mockResourceRootNode: azureResource.IAzureResourceNode = { account: mockAccount, subscription: mockSubscription, tenantId: mockTenantId, + resourceProviderId: 'mock_resource_provider', treeItem: { id: 'mock_resource_root_node', label: 'mock resource root node', @@ -134,12 +135,9 @@ describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function (): it('Should return container node when element is undefined.', async function (): Promise { const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockExtensionContext.object); - const children = await treeDataProvider.getRootChildren(); + const child = await treeDataProvider.getRootChild(); - should(children).Array(); - should(children.length).equal(1); - - const child = children[0]; + should(child).Object(); should(child.id).equal('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer'); should(child.label).equal('SQL servers'); should(child.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed); diff --git a/extensions/azurecore/src/test/azureResource/resourceService.test.ts b/extensions/azurecore/src/test/azureResource/resourceService.test.ts index c66065f63f..9a17270c14 100644 --- a/extensions/azurecore/src/test/azureResource/resourceService.test.ts +++ b/extensions/azurecore/src/test/azureResource/resourceService.test.ts @@ -8,6 +8,7 @@ import * as TypeMoq from 'typemoq'; import 'mocha'; import { fail } from 'assert'; import * as azdata from 'azdata'; +import * as vscode from 'vscode'; import { AzureResourceService } from '../../azureResource/resourceService'; import { AzureAccount, azureResource } from 'azurecore'; @@ -52,18 +53,33 @@ let mockResourceProvider1: TypeMoq.IMock; let mockResourceTreeDataProvider2: TypeMoq.IMock; let mockResourceProvider2: TypeMoq.IMock; +const mockResourceProviderId1: string = 'mock_resource_provider'; +const mockResourceNode1: azureResource.IAzureResourceNode = { + account: mockAccount, + subscription: mockSubscription, + tenantId: mockTenantId, + resourceProviderId: mockResourceProviderId1, + treeItem: { + id: 'mock_resource_node_1', + label: 'mock resource node 1', + iconPath: undefined, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: 'mock_resource_node' + } +}; + let resourceService: AzureResourceService; describe('AzureResourceService.listResourceProviderIds', function (): void { beforeEach(() => { mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider1.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider1.setup((o) => o.getRootChild()).returns(() => Promise.resolve(TypeMoq.Mock.ofType().object)); mockResourceProvider1 = TypeMoq.Mock.ofType(); mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object); mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider2.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider2.setup((o) => o.getRootChild()).returns(() => Promise.resolve(TypeMoq.Mock.ofType().object)); mockResourceProvider2 = TypeMoq.Mock.ofType(); mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2'); mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object); @@ -92,9 +108,9 @@ describe('AzureResourceService.listResourceProviderIds', function (): void { describe('AzureResourceService.getRootChildren', function (): void { beforeEach(() => { mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider1.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider1.setup((o) => o.getRootChild()).returns(() => Promise.resolve(mockResourceNode1.treeItem)); mockResourceProvider1 = TypeMoq.Mock.ofType(); - mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); + mockResourceProvider1.setup((o) => o.providerId).returns(() => mockResourceProviderId1); mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object); resourceService.clearResourceProviders(); @@ -103,15 +119,14 @@ describe('AzureResourceService.getRootChildren', function (): void { }); it('Should be correct when provider id is correct.', async function (): Promise { - const children = await resourceService.getRootChildren(mockResourceProvider1.object.providerId, mockAccount, mockSubscription); - - should(children).Array(); + const child = await resourceService.getRootChild(mockResourceProvider1.object.providerId, mockAccount, mockSubscription); + should(child).Object(); }); it('Should throw exceptions when provider id is incorrect.', async function (): Promise { const providerId = 'non_existent_provider_id'; try { - await resourceService.getRootChildren(providerId, mockAccount, mockSubscription); + await resourceService.getRootChild(providerId, mockAccount, mockSubscription); } catch (error) { should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`); return; @@ -124,7 +139,7 @@ describe('AzureResourceService.getRootChildren', function (): void { describe('AzureResourceService.getChildren', function (): void { beforeEach(() => { mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider1.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider1.setup((o) => o.getRootChild()).returns(() => Promise.resolve(TypeMoq.Mock.ofType().object)); mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); mockResourceProvider1 = TypeMoq.Mock.ofType(); mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); @@ -143,7 +158,7 @@ describe('AzureResourceService.getChildren', function (): void { it('Should throw exceptions when provider id is incorrect.', async function (): Promise { const providerId = 'non_existent_provider_id'; try { - await resourceService.getRootChildren(providerId, mockAccount, mockSubscription); + await resourceService.getRootChild(providerId, mockAccount, mockSubscription); } catch (error) { should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`); return; diff --git a/extensions/azurecore/src/test/azureResource/resourceTreeNode.test.ts b/extensions/azurecore/src/test/azureResource/resourceTreeNode.test.ts index 2efec695bf..028ba6d8c5 100644 --- a/extensions/azurecore/src/test/azureResource/resourceTreeNode.test.ts +++ b/extensions/azurecore/src/test/azureResource/resourceTreeNode.test.ts @@ -56,6 +56,7 @@ const mockResourceRootNode: azureResource.IAzureResourceNode = { account: mockAccount, subscription: mockSubscription, tenantId: mockTenantId, + resourceProviderId: mockResourceProviderId, treeItem: { id: 'mock_resource_root_node', label: 'mock resource root node', @@ -69,6 +70,7 @@ const mockResourceNode1: azureResource.IAzureResourceNode = { account: mockAccount, subscription: mockSubscription, tenantId: mockTenantId, + resourceProviderId: mockResourceProviderId, treeItem: { id: 'mock_resource_node_1', label: 'mock resource node 1', @@ -82,6 +84,7 @@ const mockResourceNode2: azureResource.IAzureResourceNode = { account: mockAccount, subscription: mockSubscription, tenantId: mockTenantId, + resourceProviderId: mockResourceProviderId, treeItem: { id: 'mock_resource_node_2', label: 'mock resource node 2', @@ -116,11 +119,7 @@ describe('AzureResourceResourceTreeNode.info', function (): void { }); it('Should be correct when created.', async function (): Promise { - const resourceTreeNode = new AzureResourceResourceTreeNode({ - resourceProviderId: mockResourceProviderId, - resourceNode: mockResourceRootNode - }, TypeMoq.Mock.ofType().object, appContext); - + const resourceTreeNode = new AzureResourceResourceTreeNode(mockResourceRootNode, TypeMoq.Mock.ofType().object, appContext); should(resourceTreeNode.nodePathValue).equal(mockResourceRootNode.treeItem.id); const treeItem = await resourceTreeNode.getTreeItem(); @@ -156,12 +155,7 @@ describe('AzureResourceResourceTreeNode.getChildren', function (): void { }); it('Should return resource nodes when it is container node.', async function (): Promise { - const resourceTreeNode = new AzureResourceResourceTreeNode({ - resourceProviderId: mockResourceProviderId, - resourceNode: mockResourceRootNode - }, - TypeMoq.Mock.ofType().object, appContext); - + const resourceTreeNode = new AzureResourceResourceTreeNode(mockResourceRootNode, TypeMoq.Mock.ofType().object, appContext); const children = await resourceTreeNode.getChildren(); mockResourceTreeDataProvider.verify((o) => o.getChildren(mockResourceRootNode), TypeMoq.Times.once()); @@ -174,27 +168,23 @@ describe('AzureResourceResourceTreeNode.getChildren', function (): void { should(child).instanceOf(AzureResourceResourceTreeNode); - const childNode = (child as AzureResourceResourceTreeNode).resourceNodeWithProviderId; + const childNode = (child as AzureResourceResourceTreeNode).resourceNode; + should(childNode.account).equal(mockAccount); + should(childNode.subscription).equal(mockSubscription); + should(childNode.tenantId).equal(mockTenantId); should(childNode.resourceProviderId).equal(mockResourceProviderId); - should(childNode.resourceNode.account).equal(mockAccount); - should(childNode.resourceNode.subscription).equal(mockSubscription); - should(childNode.resourceNode.tenantId).equal(mockTenantId); - should(childNode.resourceNode.treeItem.id).equal(mockResourceNodes[ix].treeItem.id); - should(childNode.resourceNode.treeItem.label).equal(mockResourceNodes[ix].treeItem.label); - should(childNode.resourceNode.treeItem.collapsibleState).equal(mockResourceNodes[ix].treeItem.collapsibleState); - should(childNode.resourceNode.treeItem.contextValue).equal(mockResourceNodes[ix].treeItem.contextValue); + should(childNode.treeItem.id).equal(mockResourceNodes[ix].treeItem.id); + should(childNode.treeItem.label).equal(mockResourceNodes[ix].treeItem.label); + should(childNode.treeItem.collapsibleState).equal(mockResourceNodes[ix].treeItem.collapsibleState); + should(childNode.treeItem.contextValue).equal(mockResourceNodes[ix].treeItem.contextValue); } }); it('Should return empty when it is leaf node.', async function (): Promise { - const resourceTreeNode = new AzureResourceResourceTreeNode({ - resourceProviderId: mockResourceProviderId, - resourceNode: mockResourceNode1 - }, TypeMoq.Mock.ofType().object, appContext); - + const resourceTreeNode = new AzureResourceResourceTreeNode(mockResourceNode1, TypeMoq.Mock.ofType().object, appContext); const children = await resourceTreeNode.getChildren(); - mockResourceTreeDataProvider.verify((o) => o.getRootChildren(), TypeMoq.Times.exactly(0)); + mockResourceTreeDataProvider.verify((o) => o.getRootChild(), TypeMoq.Times.exactly(0)); should(children).Array(); should(children.length).equal(0); diff --git a/extensions/azurecore/src/test/azureResource/tree/subscriptionTreeNode.test.ts b/extensions/azurecore/src/test/azureResource/tree/subscriptionTreeNode.test.ts index b5abd6aff2..100416c46b 100644 --- a/extensions/azurecore/src/test/azureResource/tree/subscriptionTreeNode.test.ts +++ b/extensions/azurecore/src/test/azureResource/tree/subscriptionTreeNode.test.ts @@ -46,6 +46,8 @@ const mockAccount: AzureAccount = { const mockTenantId: string = 'mock_tenant'; const mockSubscriptionId: string = 'mock_subscription'; +const mockResourceProviderId1: string = 'mock_resource_provider'; +const mockResourceProviderId2: string = 'mock_resource_provider'; const mockTenant: Tenant = { id: mockTenantId, @@ -60,12 +62,45 @@ const mockSubscription: azureResource.AzureResourceSubscription = { tenant: mockTenantId }; +const mockResourceNode1: azureResource.IAzureResourceNode = { + account: mockAccount, + subscription: mockSubscription, + tenantId: mockTenantId, + resourceProviderId: mockResourceProviderId1, + treeItem: { + id: 'mock_resource_node_1', + label: 'mock resource node 1', + iconPath: undefined, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: 'mock_resource_node' + } +}; + +const mockResourceNode2: azureResource.IAzureResourceNode = { + account: mockAccount, + subscription: mockSubscription, + tenantId: mockTenantId, + resourceProviderId: mockResourceProviderId2, + treeItem: { + id: 'mock_resource_node_2', + label: 'mock resource node 2', + iconPath: undefined, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: 'mock_resource_node' + } +}; + +const mockResourceNodes: azureResource.IAzureResourceNode[] = [mockResourceNode1, mockResourceNode2]; + let mockResourceTreeDataProvider1: TypeMoq.IMock; let mockResourceProvider1: TypeMoq.IMock; let mockResourceTreeDataProvider2: TypeMoq.IMock; let mockResourceProvider2: TypeMoq.IMock; +let mockUniversalTreeDataProvider: TypeMoq.IMock; +let mockUniversalResourceProvider: TypeMoq.IMock; + const resourceService: AzureResourceService = new AzureResourceService(); describe('AzureResourceSubscriptionTreeNode.info', function (): void { @@ -78,14 +113,14 @@ describe('AzureResourceSubscriptionTreeNode.info', function (): void { mockTreeChangeHandler = TypeMoq.Mock.ofType(); mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider1.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider1.setup((o) => o.getRootChild()).returns(() => Promise.resolve(TypeMoq.Mock.ofType().object)); mockResourceTreeDataProvider1.setup((x: any) => x.then).returns(() => undefined); mockResourceProvider1 = TypeMoq.Mock.ofType(); mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object); mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider2.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider2.setup((o) => o.getRootChild()).returns(() => Promise.resolve(TypeMoq.Mock.ofType().object)); mockResourceTreeDataProvider2.setup((x: any) => x.then).returns(() => undefined); mockResourceProvider2 = TypeMoq.Mock.ofType(); mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2'); @@ -130,21 +165,28 @@ describe('AzureResourceSubscriptionTreeNode.getChildren', function (): void { mockTreeChangeHandler = TypeMoq.Mock.ofType(); mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider1.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([{ label: 'Item1' }] as azdata.TreeItem[])); + mockResourceTreeDataProvider1.setup((o) => o.getRootChild()).returns(() => Promise.resolve({ label: 'Item1' } as azdata.TreeItem)); mockResourceProvider1 = TypeMoq.Mock.ofType(); - mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); + mockResourceProvider1.setup((o) => o.providerId).returns(() => mockResourceProviderId1); mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object); mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType(); - mockResourceTreeDataProvider2.setup((o) => o.getRootChildren()).returns(() => Promise.resolve([{ label: 'Item2' }] as azdata.TreeItem[])); + mockResourceTreeDataProvider2.setup((o) => o.getRootChild()).returns(() => Promise.resolve({ label: 'Item2' } as azdata.TreeItem)); mockResourceProvider2 = TypeMoq.Mock.ofType(); - mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2'); + mockResourceProvider2.setup((o) => o.providerId).returns(() => mockResourceProviderId2); mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object); + mockUniversalTreeDataProvider = TypeMoq.Mock.ofType(); + mockUniversalTreeDataProvider.setup((o) => o.getAllChildren(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(mockResourceNodes)); + mockUniversalResourceProvider = TypeMoq.Mock.ofType(); + mockUniversalResourceProvider.setup((o) => o.providerId).returns(() => 'mockUniversalResourceProvider'); + mockUniversalResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockUniversalTreeDataProvider.object); + resourceService.clearResourceProviders(); resourceService.registerResourceProvider(mockResourceProvider1.object); resourceService.registerResourceProvider(mockResourceProvider2.object); + resourceService.registerUniversalResourceProvider(mockUniversalResourceProvider.object); resourceService.areResourceProvidersLoaded = true; appContext = new AppContext(mockExtensionContext.object); @@ -157,14 +199,12 @@ describe('AzureResourceSubscriptionTreeNode.getChildren', function (): void { const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenant, appContext, mockTreeChangeHandler.object, TypeMoq.Mock.ofType().object); const children = await subscriptionTreeNode.getChildren(); - mockResourceTreeDataProvider1.verify((o) => o.getRootChildren(), TypeMoq.Times.once()); - - mockResourceTreeDataProvider2.verify((o) => o.getRootChildren(), TypeMoq.Times.once()); + mockUniversalTreeDataProvider.verify((o) => o.getAllChildren(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); const expectedResourceProviderIds = await resourceService.listResourceProviderIds(); should(children).Array(); - should(children.length).equal(expectedResourceProviderIds.length, 'There should be one child for each resource provider'); + should(children.length).equal(expectedResourceProviderIds.length, 'There should be one child for each resource provider that has a resource.'); for (const child of children) { should(child).instanceOf(AzureResourceResourceTreeNode); }