From ae8fd6f4690407bc79786382bc16baccce62dd3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 06:42:30 +0000 Subject: [PATCH 01/13] Initial plan From f5d8843e6154be004a53a94cd71911b74e12e076 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 07:02:12 +0000 Subject: [PATCH 02/13] Implement Phase 1-3: Azure MongoDB (RU) Service Discovery infrastructure Co-authored-by: tnaum-ms <171359267+tnaum-ms@users.noreply.github.com> --- l10n/bundle.l10n.json | 7 + src/documentdb/ClustersExtension.ts | 2 + .../AzureMongoRUDiscoveryProvider.ts | 61 +++++++++ .../AzureMongoRUServiceRootItem.ts | 93 +++++++++++++ .../AzureMongoRUSubscriptionItem.ts | 79 +++++++++++ .../documentdb/MongoRUResourceItem.ts | 122 +++++++++++++++++ .../AzureMongoRUExecuteStep.ts | 59 +++++++++ .../discovery-wizard/SelectRUClusterStep.ts | 71 ++++++++++ .../utils/ruClusterHelpers.ts | 124 ++++++++++++++++++ 9 files changed, 618 insertions(+) create mode 100644 src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts create mode 100644 src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts create mode 100644 src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts create mode 100644 src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts create mode 100644 src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts create mode 100644 src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts create mode 100644 src/plugins/service-azure-mongo-ru/utils/ruClusterHelpers.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 91aa419a3..d796879f0 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -35,6 +35,8 @@ "A connection with the same username and host already exists.": "A connection with the same username and host already exists.", "A new connection will be added to your Connections View.\nDo you want to continue?\n\nNote: You can disable these URL handling confirmations in the exension settings.": "A new connection will be added to your Connections View.\nDo you want to continue?\n\nNote: You can disable these URL handling confirmations in the exension settings.", "A value is required to proceed.": "A value is required to proceed.", + "Account \"{account}\" is not a MongoDB account.": "Account \"{account}\" is not a MongoDB account.", + "Account information is incomplete.": "Account information is incomplete.", "Add new document": "Add new document", "Advanced": "Advanced", "All available providers have been added already.": "All available providers have been added already.", @@ -56,9 +58,11 @@ "Authentication is required to run this action.": "Authentication is required to run this action.", "Authentication is required to use this migration provider.": "Authentication is required to use this migration provider.", "Azure Activity": "Azure Activity", + "Azure Cosmos DB for MongoDB (RU)": "Azure Cosmos DB for MongoDB (RU)", "Azure Cosmos DB for MongoDB (RU) Emulator": "Azure Cosmos DB for MongoDB (RU) Emulator", "Azure Cosmos DB for MongoDB (vCore)": "Azure Cosmos DB for MongoDB (vCore)", "Azure Service Discovery": "Azure Service Discovery", + "Azure Service Discovery for MongoDB RU": "Azure Service Discovery for MongoDB RU", "Azure VM Service Discovery": "Azure VM Service Discovery", "Azure VM: Attempting to authenticate with \"{vmName}\"…": "Azure VM: Attempting to authenticate with \"{vmName}\"…", "Azure VM: Connected to \"{vmName}\" as \"{username}\".": "Azure VM: Connected to \"{vmName}\" as \"{username}\".", @@ -70,6 +74,7 @@ "Change page size": "Change page size", "Check document syntax": "Check document syntax", "Choose a cluster…": "Choose a cluster…", + "Choose a RU cluster…": "Choose a RU cluster…", "Choose a subscription…": "Choose a subscription…", "Choose a Virtual Machine…": "Choose a Virtual Machine…", "Choose the data migration provider…": "Choose the data migration provider…", @@ -270,6 +275,7 @@ "Loading document {num} of {countUri}": "Loading document {num} of {countUri}", "Loading documents…": "Loading documents…", "Loading resources...": "Loading resources...", + "Loading RU clusters…": "Loading RU clusters…", "Loading subscriptions…": "Loading subscriptions…", "Loading Virtual Machines…": "Loading Virtual Machines…", "Loading...": "Loading...", @@ -434,6 +440,7 @@ "Unable to connect to the local database instance. Make sure it is started correctly. See {link} for tips.": "Unable to connect to the local database instance. Make sure it is started correctly. See {link} for tips.", "Unable to connect to the local instance. Make sure it is started correctly. See {link} for tips.": "Unable to connect to the local instance. Make sure it is started correctly. See {link} for tips.", "Unable to parse syntax near line {line}, col {column}: {message}": "Unable to parse syntax near line {line}, col {column}: {message}", + "Unable to retrieve credentials for cluster \"{cluster}\".": "Unable to retrieve credentials for cluster \"{cluster}\".", "Unable to retrieve credentials for the selected cluster.": "Unable to retrieve credentials for the selected cluster.", "Unexpected status code: {0}": "Unexpected status code: {0}", "Unknown error": "Unknown error", diff --git a/src/documentdb/ClustersExtension.ts b/src/documentdb/ClustersExtension.ts index f5c20cd71..e5b319a33 100644 --- a/src/documentdb/ClustersExtension.ts +++ b/src/documentdb/ClustersExtension.ts @@ -47,6 +47,7 @@ import { updateConnectionString } from '../commands/updateConnectionString/updat import { updateCredentials } from '../commands/updateCredentials/updateCredentials'; import { isVCoreAndRURolloutEnabled } from '../extension'; import { ext } from '../extensionVariables'; +import { AzureMongoRUDiscoveryProvider } from '../plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider'; import { AzureVMDiscoveryProvider } from '../plugins/service-azure-vm/AzureVMDiscoveryProvider'; import { AzureDiscoveryProvider } from '../plugins/service-azure/AzureDiscoveryProvider'; import { DiscoveryService } from '../services/discoveryServices'; @@ -70,6 +71,7 @@ export class ClustersExtension implements vscode.Disposable { registerDiscoveryServices(_activateContext: IActionContext) { DiscoveryService.registerProvider(new AzureDiscoveryProvider()); + DiscoveryService.registerProvider(new AzureMongoRUDiscoveryProvider()); DiscoveryService.registerProvider(new AzureVMDiscoveryProvider()); } diff --git a/src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts b/src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts new file mode 100644 index 000000000..4b2f251f0 --- /dev/null +++ b/src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type IActionContext, type IWizardOptions } from '@microsoft/vscode-azext-utils'; +import { Disposable, l10n, ThemeIcon } from 'vscode'; +import { type NewConnectionWizardContext } from '../../commands/newConnection/NewConnectionWizardContext'; +import { ext } from '../../extensionVariables'; +import { type DiscoveryProvider } from '../../services/discoveryServices'; +import { type TreeElement } from '../../tree/TreeElement'; +import { AzureSubscriptionProviderWithFilters } from '../api-shared/azure/AzureSubscriptionProviderWithFilters'; +import { configureAzureSubscriptionFilter } from '../api-shared/azure/subscriptionFiltering'; +import { AzureContextProperties } from '../service-azure/AzureDiscoveryProvider'; +import { SelectSubscriptionStep } from '../service-azure/discovery-wizard/SelectSubscriptionStep'; +import { AzureMongoRUServiceRootItem } from './discovery-tree/AzureMongoRUServiceRootItem'; +import { AzureMongoRUExecuteStep } from './discovery-wizard/AzureMongoRUExecuteStep'; +import { SelectRUClusterStep } from './discovery-wizard/SelectRUClusterStep'; + +export class AzureMongoRUDiscoveryProvider extends Disposable implements DiscoveryProvider { + id = 'azure-mongo-ru-discovery'; + label = l10n.t('Azure Cosmos DB for MongoDB (RU)'); + description = l10n.t('Azure Service Discovery for MongoDB RU'); + iconPath = new ThemeIcon('azure'); + + azureSubscriptionProvider: AzureSubscriptionProviderWithFilters; + + constructor() { + super(() => { + this.azureSubscriptionProvider.dispose(); + }); + + this.azureSubscriptionProvider = new AzureSubscriptionProviderWithFilters(); + } + + getDiscoveryTreeRootItem(parentId: string): TreeElement { + return new AzureMongoRUServiceRootItem(this.azureSubscriptionProvider, parentId); + } + + getDiscoveryWizard(context: NewConnectionWizardContext): IWizardOptions { + context.properties[AzureContextProperties.AzureSubscriptionProvider] = this.azureSubscriptionProvider; + + return { + title: l10n.t('Azure Service Discovery'), + promptSteps: [new SelectSubscriptionStep(), new SelectRUClusterStep()], + executeSteps: [new AzureMongoRUExecuteStep()], + showLoadingPrompt: true, + }; + } + + getLearnMoreUrl(): string | undefined { + return 'https://aka.ms/vscode-documentdb-discovery-providers-azure-ru'; + } + + async configureTreeItemFilter(context: IActionContext, node: TreeElement): Promise { + if (node instanceof AzureMongoRUServiceRootItem) { + await configureAzureSubscriptionFilter(context, this.azureSubscriptionProvider); + ext.discoveryBranchDataProvider.refresh(node); + } + } +} \ No newline at end of file diff --git a/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts new file mode 100644 index 000000000..2579b0baa --- /dev/null +++ b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureauth'; +import * as l10n from '@vscode/l10n'; +import * as vscode from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { createGenericElementWithContext } from '../../../tree/api/createGenericElementWithContext'; +import { type ExtTreeElementBase, type TreeElement } from '../../../tree/TreeElement'; +import { + isTreeElementWithContextValue, + type TreeElementWithContextValue, +} from '../../../tree/TreeElementWithContextValue'; +import { type TreeElementWithRetryChildren } from '../../../tree/TreeElementWithRetryChildren'; +import { AzureMongoRUSubscriptionItem } from './AzureMongoRUSubscriptionItem'; + +export class AzureMongoRUServiceRootItem implements TreeElement, TreeElementWithContextValue, TreeElementWithRetryChildren { + public readonly id: string; + public contextValue: string = + 'enableRefreshCommand;enableFilterCommand;enableLearnMoreCommand;azureMongoRUService'; + + constructor( + private readonly azureSubscriptionProvider: VSCodeAzureSubscriptionProvider, + public readonly parentId: string, + ) { + this.id = `${parentId}/azure-mongo-ru-discovery`; + } + + async getChildren(): Promise { + /** + * This is an important step to ensure that the user is signed in to Azure before listing subscriptions. + */ + if (!(await this.azureSubscriptionProvider.isSignedIn())) { + const signIn: vscode.MessageItem = { title: l10n.t('Sign In') }; + void vscode.window + .showInformationMessage(l10n.t('You are not signed in to Azure. Sign in and retry.'), signIn) + .then(async (input) => { + if (input === signIn) { + await this.azureSubscriptionProvider.signIn(); + ext.discoveryBranchDataProvider.refresh(); + } + }); + + return [ + createGenericElementWithContext({ + contextValue: 'error', // note: keep this in sync with the `hasRetryNode` function in this file + id: `${this.id}/retry`, + label: vscode.l10n.t('Click here to retry'), + iconPath: new vscode.ThemeIcon('refresh'), + commandId: 'vscode-documentdb.command.internal.retry', + commandArgs: [this], + }), + ]; + } + + const subscriptions = await this.azureSubscriptionProvider.getSubscriptions(true); + if (!subscriptions || subscriptions.length === 0) { + return []; + } + + return ( + subscriptions + // sort by name + .sort((a, b) => a.name.localeCompare(b.name)) + // map to AzureMongoRUSubscriptionItem + .map((sub) => { + return new AzureMongoRUSubscriptionItem(this.id, { + subscription: sub, + subscriptionName: sub.name, + subscriptionId: sub.subscriptionId, + }); + }) + ); + } + + public hasRetryNode(children: TreeElement[] | null | undefined): boolean { + return ( + children?.some((child) => isTreeElementWithContextValue(child) && child.contextValue === 'error') ?? false + ); + } + + public getTreeItem(): vscode.TreeItem { + return { + id: this.id, + contextValue: this.contextValue, + label: l10n.t('Azure Cosmos DB for MongoDB (RU)'), + iconPath: new vscode.ThemeIcon('azure'), + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + }; + } +} \ No newline at end of file diff --git a/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts new file mode 100644 index 000000000..f76f39c03 --- /dev/null +++ b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getResourceGroupFromId, uiUtils } from '@microsoft/vscode-azext-azureutils'; +import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; +import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; +import * as vscode from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { type TreeElement } from '../../../tree/TreeElement'; +import { type TreeElementWithContextValue } from '../../../tree/TreeElementWithContextValue'; +import { type ClusterModel } from '../../../tree/documentdb/ClusterModel'; +import { createCosmosDBManagementClient } from '../../../utils/azureClients'; +import { nonNullProp } from '../../../utils/nonNull'; +import { MongoRUResourceItem } from './documentdb/MongoRUResourceItem'; + +export interface AzureSubscriptionModel { + subscriptionName: string; + subscription: AzureSubscription; + subscriptionId: string; +} + +export class AzureMongoRUSubscriptionItem implements TreeElement, TreeElementWithContextValue { + public readonly id: string; + public contextValue: string = 'enableRefreshCommand;azureMongoRUSubscription'; + + constructor( + public readonly parentId: string, + public readonly subscription: AzureSubscriptionModel, + ) { + this.id = `${parentId}/${subscription.subscriptionId}`; + } + + async getChildren(): Promise { + return await callWithTelemetryAndErrorHandling( + 'azure-mongo-ru-discovery.getChildren', + async (context: IActionContext) => { + context.telemetry.properties.discoveryProvider = 'azure-mongo-ru-discovery'; + + const managementClient = await createCosmosDBManagementClient(context, this.subscription.subscription); + const allAccounts = await uiUtils.listAllIterator(managementClient.databaseAccounts.list()); + const accounts = allAccounts.filter((account) => account.kind === 'MongoDB'); + + return accounts + .sort((a, b) => (a.name || '').localeCompare(b.name || '')) + .map((account) => { + const resourceId = nonNullProp(account, 'id', 'account.id', 'AzureMongoRUSubscriptionItem.ts'); + + const clusterInfo: ClusterModel = { + ...account, + resourceGroup: getResourceGroupFromId(resourceId), + } as ClusterModel; + + return new MongoRUResourceItem(this.subscription.subscription, clusterInfo); + }); + }, + ); + } + + public getTreeItem(): vscode.TreeItem { + return { + id: this.id, + contextValue: this.contextValue, + label: this.subscription.subscriptionName, + tooltip: `Subscription ID: ${this.subscription.subscriptionId}`, + iconPath: vscode.Uri.joinPath( + ext.context.extensionUri, + 'resources', + 'from_node_modules', + '@microsoft', + 'vscode-azext-azureutils', + 'resources', + 'azureSubscription.svg', + ), + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + }; + } +} \ No newline at end of file diff --git a/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts b/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts new file mode 100644 index 000000000..afd65b3e6 --- /dev/null +++ b/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; +import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; +import * as l10n from '@vscode/l10n'; +import * as vscode from 'vscode'; +import { ClustersClient } from '../../../../documentdb/ClustersClient'; +import { CredentialCache } from '../../../../documentdb/CredentialCache'; +import { Views } from '../../../../documentdb/Views'; +import { ext } from '../../../../extensionVariables'; +import { ClusterItemBase, type ClusterCredentials } from '../../../../tree/documentdb/ClusterItemBase'; +import { type ClusterModel } from '../../../../tree/documentdb/ClusterModel'; +import { extractCredentialsFromRUAccount, getRUClusterInformationFromAzure } from '../../utils/ruClusterHelpers'; + +export class MongoRUResourceItem extends ClusterItemBase { + iconPath = vscode.Uri.joinPath( + ext.context.extensionUri, + 'resources', + 'from_node_modules', + '@microsoft', + 'vscode-azext-azureutils', + 'resources', + 'azureIcons', + 'MongoClusters.svg', + ); + + constructor( + readonly subscription: AzureSubscription, + cluster: ClusterModel, + ) { + super(cluster); + this.contextValue = 'mongoRUResource'; + } + + public async getCredentials(): Promise { + return callWithTelemetryAndErrorHandling('getCredentials', async (context: IActionContext) => { + context.telemetry.properties.view = Views.DiscoveryView; + context.telemetry.properties.discoveryProvider = 'azure-mongo-ru-discovery'; + + // Retrieve and validate cluster information (throws if invalid) + const accountInformation = await getRUClusterInformationFromAzure( + context, + this.subscription, + this.cluster.resourceGroup!, + this.cluster.name, + ); + + return extractCredentialsFromRUAccount(context, accountInformation); + }); + } + + /** + * Authenticates and connects to the MongoDB cluster. + * @returns An instance of ClustersClient if successful; otherwise, null. + */ + protected async authenticateAndConnect(): Promise { + const result = await callWithTelemetryAndErrorHandling('connect', async (context: IActionContext) => { + context.telemetry.properties.view = Views.DiscoveryView; + context.telemetry.properties.discoveryProvider = 'azure-mongo-ru-discovery'; + + ext.outputChannel.appendLine( + l10n.t('Attempting to authenticate with "{cluster}"…', { + cluster: this.cluster.name, + }), + ); + + try { + // Get credentials for this cluster + const credentials = await this.getCredentials(); + if (!credentials) { + throw new Error(l10n.t('Unable to retrieve credentials for cluster "{cluster}".', { + cluster: this.cluster.name, + })); + } + + // Cache the credentials for this cluster + CredentialCache.setAuthCredentials( + this.id, + credentials.selectedAuthMethod ?? credentials.availableAuthMethods[0], + credentials.connectionString, + credentials.connectionUser, + credentials.connectionPassword, + ); + + // Connect using the cached credentials + const clustersClient = await ClustersClient.getClient(this.id); + + ext.outputChannel.appendLine( + l10n.t('Connected to the cluster "{cluster}".', { + cluster: this.cluster.name, + }), + ); + + return clustersClient; + } catch (error) { + ext.outputChannel.appendLine(l10n.t('Error: {error}', { error: (error as Error).message })); + + void vscode.window.showErrorMessage( + l10n.t('Failed to connect to "{cluster}"', { cluster: this.cluster.name }), + { + modal: true, + detail: + l10n.t('Revisit connection details and try again.') + + '\n\n' + + l10n.t('Error: {error}', { error: (error as Error).message }), + }, + ); + + // Clean up failed connection + await ClustersClient.deleteClient(this.id); + CredentialCache.deleteCredentials(this.id); + + return null; + } + }); + + return result ?? null; + } +} \ No newline at end of file diff --git a/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts b/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts new file mode 100644 index 000000000..e865512d5 --- /dev/null +++ b/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getResourceGroupFromId } from '@microsoft/vscode-azext-azureutils'; +import { AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils'; +import { type NewConnectionWizardContext } from '../../../commands/newConnection/NewConnectionWizardContext'; + +import { type GenericResource } from '@azure/arm-resources'; +import { type AzureSubscription } from '@microsoft/vscode-azext-azureauth'; +import { AzureContextProperties } from '../../service-azure/AzureDiscoveryProvider'; +import { extractCredentialsFromRUAccount, getRUClusterInformationFromAzure } from '../utils/ruClusterHelpers'; + +export class AzureMongoRUExecuteStep extends AzureWizardExecuteStep { + public priority: number = -1; + + public async execute(context: NewConnectionWizardContext): Promise { + if (context.properties[AzureContextProperties.SelectedSubscription] === undefined) { + throw new Error('SelectedSubscription is not set.'); + } + if (context.properties[AzureContextProperties.SelectedCluster] === undefined) { + throw new Error('SelectedCluster is not set.'); + } + + context.telemetry.properties.discoveryProvider = 'azure-mongo-ru-discovery'; + + const subscription = context.properties[ + AzureContextProperties.SelectedSubscription + ] as unknown as AzureSubscription; + + const cluster = context.properties[AzureContextProperties.SelectedCluster] as unknown as GenericResource; + + const resourceGroup = getResourceGroupFromId(cluster.id!); + + const accountInformation = await getRUClusterInformationFromAzure( + context, + subscription, + resourceGroup, + cluster.name!, + ); + + const credentials = await extractCredentialsFromRUAccount(context, accountInformation); + + context.connectionString = credentials.connectionString; + context.username = credentials.connectionUser; + context.password = credentials.connectionPassword; + context.availableAuthenticationMethods = credentials.availableAuthMethods; + + // clean-up + context.properties[AzureContextProperties.SelectedSubscription] = undefined; + context.properties[AzureContextProperties.SelectedCluster] = undefined; + context.properties[AzureContextProperties.AzureSubscriptionProvider] = undefined; + } + + public shouldExecute(): boolean { + return true; + } +} \ No newline at end of file diff --git a/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts b/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts new file mode 100644 index 000000000..79ad757dd --- /dev/null +++ b/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { uiUtils } from '@microsoft/vscode-azext-azureutils'; +import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils'; +import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; +import * as l10n from '@vscode/l10n'; +import { Uri, type QuickPickItem } from 'vscode'; +import { type NewConnectionWizardContext } from '../../../commands/newConnection/NewConnectionWizardContext'; +import { ext } from '../../../extensionVariables'; +import { createCosmosDBManagementClient } from '../../../utils/azureClients'; +import { AzureContextProperties } from '../../service-azure/AzureDiscoveryProvider'; + +export class SelectRUClusterStep extends AzureWizardPromptStep { + iconPath = Uri.joinPath( + ext.context.extensionUri, + 'resources', + 'from_node_modules', + '@microsoft', + 'vscode-azext-azureutils', + 'resources', + 'azureIcons', + 'MongoClusters.svg', + ); + + public async prompt(context: NewConnectionWizardContext): Promise { + if ( + context.properties[AzureContextProperties.SelectedSubscription] === undefined + ) { + throw new Error('SelectedSubscription is not set.'); + } + + const managementClient = await createCosmosDBManagementClient( + context, + context.properties[AzureContextProperties.SelectedSubscription] as unknown as AzureSubscription, + ); + + const allAccounts = await uiUtils.listAllIterator(managementClient.databaseAccounts.list()); + const accounts = allAccounts.filter((account) => account.kind === 'MongoDB'); + + const promptItems: (QuickPickItem & { id: string })[] = accounts + .map((account) => ({ + id: account.id!, + label: account.name!, + description: account.id, + iconPath: this.iconPath, + + alwaysShow: true, + })) + .sort((a, b) => a.label.localeCompare(b.label)); + + const selectedItem = await context.ui.showQuickPick([...promptItems], { + stepName: 'selectRUCluster', + placeHolder: l10n.t('Choose a RU cluster…'), + loadingPlaceHolder: l10n.t('Loading RU clusters…'), + enableGrouping: true, + matchOnDescription: true, + suppressPersistence: true, + }); + + context.properties[AzureContextProperties.SelectedCluster] = accounts.find( + (account) => account.id === selectedItem.id, + ); + } + + public shouldPrompt(): boolean { + return true; + } +} \ No newline at end of file diff --git a/src/plugins/service-azure-mongo-ru/utils/ruClusterHelpers.ts b/src/plugins/service-azure-mongo-ru/utils/ruClusterHelpers.ts new file mode 100644 index 000000000..11f968e03 --- /dev/null +++ b/src/plugins/service-azure-mongo-ru/utils/ruClusterHelpers.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { type IActionContext } from '@microsoft/vscode-azext-utils'; +import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; +import * as l10n from '@vscode/l10n'; +import { AuthMethodId } from '../../../documentdb/auth/AuthMethod'; +import { maskSensitiveValuesInTelemetry } from '../../../documentdb/utils/connectionStringHelpers'; +import { DocumentDBConnectionString } from '../../../documentdb/utils/DocumentDBConnectionString'; +import { type ClusterCredentials } from '../../../tree/documentdb/ClusterItemBase'; +import { createCosmosDBManagementClient } from '../../../utils/azureClients'; + +/** + * Retrieves cluster information from Azure for RU accounts. + */ +export async function getRUClusterInformationFromAzure( + context: IActionContext, + subscription: AzureSubscription, + resourceGroup: string, + accountName: string, +): Promise { + // subscription comes from different azure packages in callers; cast here intentionally + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + const managementClient = await createCosmosDBManagementClient(context, subscription as any); + + const account = await managementClient.databaseAccounts.get(resourceGroup, accountName); + + // Validate that this is a MongoDB RU account + if ((account as { kind?: string }).kind !== 'MongoDB') { + context.telemetry.properties.error = 'invalid-account-kind'; + throw new Error( + l10n.t('Account "{account}" is not a MongoDB account.', { + account: accountName, + }), + ); + } + + return account; +} + +/** + * Extracts credentials from RU account information. + */ +export async function extractCredentialsFromRUAccount( + context: IActionContext, + account: unknown, +): Promise { + // subscription comes from different azure packages in callers; cast here intentionally + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + const managementClient = await createCosmosDBManagementClient(context, account as any); + + const resourceGroup = (account as { id?: string }).id?.split('/')[4]; + const accountName = (account as { name?: string }).name; + + if (!resourceGroup || !accountName) { + throw new Error(l10n.t('Account information is incomplete.')); + } + + const connectionStringsList = await managementClient.databaseAccounts.listConnectionStrings( + resourceGroup, + accountName, + ); + + /** + * databaseAccounts.listConnectionStrings returns an array of (typically 4) connection string objects: + * + * interface DatabaseAccountConnectionString { + * readonly connectionString?: string; + * readonly description?: string; + * readonly keyKind?: Kind; + * readonly type?: Type; + * } + * + * Today we're interested in the one where "keyKind" is "Primary", but this might change in the future. + * Other known values: + * - Primary + * - Secondary + * - PrimaryReadonly + * - SecondaryReadonly + */ + + // More efficient approach + const primaryConnectionString = connectionStringsList?.connectionStrings?.find( + (cs) => cs.keyKind?.toLowerCase() === 'primary', + )?.connectionString; + + // Validate connection string's presence + if (!primaryConnectionString) { + context.telemetry.properties.error = 'missing-connection-string'; + throw new Error( + l10n.t('Authentication data (primary connection string) is missing for "{cluster}".', { + cluster: accountName, + }), + ); + } + + context.valuesToMask.push(primaryConnectionString); + + const parsedCS = new DocumentDBConnectionString(primaryConnectionString); + maskSensitiveValuesInTelemetry(context, parsedCS); + + const username = parsedCS.username; + const password = parsedCS.password; + // do not keep secrets in the connection string + parsedCS.username = ''; + parsedCS.password = ''; + + // the connection string received sometimes contains an 'appName' entry + // with a value that's not escaped, let's just remove it as we don't use + // it here anyway. + parsedCS.searchParams.delete('appName'); + + const clusterCredentials: ClusterCredentials = { + connectionString: parsedCS.toString(), + connectionUser: username, + connectionPassword: password, + availableAuthMethods: [AuthMethodId.NativeAuth], + selectedAuthMethod: AuthMethodId.NativeAuth, + }; + + return clusterCredentials; +} \ No newline at end of file From 398614c998f3ec6b95b7022666673a918a88e137 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 13:18:55 +0200 Subject: [PATCH 03/13] Wording mistake in vCore discovery --- src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts b/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts index 9dd3e95e1..f77af641a 100644 --- a/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts +++ b/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts @@ -56,7 +56,7 @@ export class SelectClusterStep extends AzureWizardPromptStep Date: Fri, 5 Sep 2025 13:39:57 +0200 Subject: [PATCH 04/13] Refactor: Moved shared code to the 'api' folder --- .../azure/tree}/AzureSubscriptionItem.ts | 16 ++++++++-------- .../azure/wizard/AzureContextProperties.ts | 10 ++++++++++ .../azure/wizard}/SelectSubscriptionStep.ts | 6 +++--- .../AzureMongoRUDiscoveryProvider.ts | 6 +++--- .../AzureMongoRUSubscriptionItem.ts | 6 ++++-- .../discovery-wizard/AzureMongoRUExecuteStep.ts | 6 +++--- .../discovery-wizard/SelectRUClusterStep.ts | 8 +++----- .../service-azure/AzureDiscoveryProvider.ts | 9 ++------- .../discovery-tree/AzureServiceRootItem.ts | 2 +- .../discovery-wizard/AzureExecuteStep.ts | 2 +- 10 files changed, 38 insertions(+), 33 deletions(-) rename src/plugins/{service-azure/discovery-tree => api-shared/azure/tree}/AzureSubscriptionItem.ts (83%) create mode 100644 src/plugins/api-shared/azure/wizard/AzureContextProperties.ts rename src/plugins/{service-azure/discovery-wizard => api-shared/azure/wizard}/SelectSubscriptionStep.ts (93%) diff --git a/src/plugins/service-azure/discovery-tree/AzureSubscriptionItem.ts b/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts similarity index 83% rename from src/plugins/service-azure/discovery-tree/AzureSubscriptionItem.ts rename to src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts index e6687fc4b..fa3257d4b 100644 --- a/src/plugins/service-azure/discovery-tree/AzureSubscriptionItem.ts +++ b/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts @@ -7,14 +7,14 @@ import { getResourceGroupFromId, uiUtils } from '@microsoft/vscode-azext-azureut import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import * as vscode from 'vscode'; -import { DocumentDBExperience } from '../../../DocumentDBExperiences'; -import { ext } from '../../../extensionVariables'; -import { type TreeElement } from '../../../tree/TreeElement'; -import { type TreeElementWithContextValue } from '../../../tree/TreeElementWithContextValue'; -import { type ClusterModel } from '../../../tree/documentdb/ClusterModel'; -import { createResourceManagementClient } from '../../../utils/azureClients'; -import { nonNullProp } from '../../../utils/nonNull'; -import { DocumentDBResourceItem } from './documentdb/DocumentDBResourceItem'; +import { DocumentDBExperience } from '../../../../DocumentDBExperiences'; +import { ext } from '../../../../extensionVariables'; +import { type TreeElement } from '../../../../tree/TreeElement'; +import { type TreeElementWithContextValue } from '../../../../tree/TreeElementWithContextValue'; +import { type ClusterModel } from '../../../../tree/documentdb/ClusterModel'; +import { createResourceManagementClient } from '../../../../utils/azureClients'; +import { nonNullProp } from '../../../../utils/nonNull'; +import { DocumentDBResourceItem } from '../../../service-azure/discovery-tree/documentdb/DocumentDBResourceItem'; export interface AzureSubscriptionModel { subscriptionName: string; diff --git a/src/plugins/api-shared/azure/wizard/AzureContextProperties.ts b/src/plugins/api-shared/azure/wizard/AzureContextProperties.ts new file mode 100644 index 000000000..6ec698d0f --- /dev/null +++ b/src/plugins/api-shared/azure/wizard/AzureContextProperties.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export enum AzureContextProperties { + AzureSubscriptionProvider = 'azureSubscriptionProvider', + SelectedSubscription = 'selectedSubscription', + SelectedCluster = 'selectedCluster', +} diff --git a/src/plugins/service-azure/discovery-wizard/SelectSubscriptionStep.ts b/src/plugins/api-shared/azure/wizard/SelectSubscriptionStep.ts similarity index 93% rename from src/plugins/service-azure/discovery-wizard/SelectSubscriptionStep.ts rename to src/plugins/api-shared/azure/wizard/SelectSubscriptionStep.ts index b970f9ec5..54bbe9a9d 100644 --- a/src/plugins/service-azure/discovery-wizard/SelectSubscriptionStep.ts +++ b/src/plugins/api-shared/azure/wizard/SelectSubscriptionStep.ts @@ -7,9 +7,9 @@ import { VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureau import { AzureWizardPromptStep, UserCancelledError } from '@microsoft/vscode-azext-utils'; import * as l10n from '@vscode/l10n'; import { Uri, window, type MessageItem, type QuickPickItem } from 'vscode'; -import { type NewConnectionWizardContext } from '../../../commands/newConnection/NewConnectionWizardContext'; -import { ext } from '../../../extensionVariables'; -import { AzureContextProperties } from '../AzureDiscoveryProvider'; +import { type NewConnectionWizardContext } from '../../../../commands/newConnection/NewConnectionWizardContext'; +import { ext } from '../../../../extensionVariables'; +import { AzureContextProperties } from './AzureContextProperties'; export class SelectSubscriptionStep extends AzureWizardPromptStep { iconPath = Uri.joinPath( diff --git a/src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts b/src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts index 4b2f251f0..34211b5bd 100644 --- a/src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts +++ b/src/plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider.ts @@ -11,8 +11,8 @@ import { type DiscoveryProvider } from '../../services/discoveryServices'; import { type TreeElement } from '../../tree/TreeElement'; import { AzureSubscriptionProviderWithFilters } from '../api-shared/azure/AzureSubscriptionProviderWithFilters'; import { configureAzureSubscriptionFilter } from '../api-shared/azure/subscriptionFiltering'; -import { AzureContextProperties } from '../service-azure/AzureDiscoveryProvider'; -import { SelectSubscriptionStep } from '../service-azure/discovery-wizard/SelectSubscriptionStep'; +import { AzureContextProperties } from '../api-shared/azure/wizard/AzureContextProperties'; +import { SelectSubscriptionStep } from '../service-azure-vm/discovery-wizard/SelectSubscriptionStep'; import { AzureMongoRUServiceRootItem } from './discovery-tree/AzureMongoRUServiceRootItem'; import { AzureMongoRUExecuteStep } from './discovery-wizard/AzureMongoRUExecuteStep'; import { SelectRUClusterStep } from './discovery-wizard/SelectRUClusterStep'; @@ -58,4 +58,4 @@ export class AzureMongoRUDiscoveryProvider extends Disposable implements Discove ext.discoveryBranchDataProvider.refresh(node); } } -} \ No newline at end of file +} diff --git a/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts index f76f39c03..a6cdd22db 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUSubscriptionItem.ts @@ -7,6 +7,7 @@ import { getResourceGroupFromId, uiUtils } from '@microsoft/vscode-azext-azureut import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import * as vscode from 'vscode'; +import { CosmosDBMongoRUExperience } from '../../../DocumentDBExperiences'; import { ext } from '../../../extensionVariables'; import { type TreeElement } from '../../../tree/TreeElement'; import { type TreeElementWithContextValue } from '../../../tree/TreeElementWithContextValue'; @@ -37,7 +38,7 @@ export class AzureMongoRUSubscriptionItem implements TreeElement, TreeElementWit 'azure-mongo-ru-discovery.getChildren', async (context: IActionContext) => { context.telemetry.properties.discoveryProvider = 'azure-mongo-ru-discovery'; - + const managementClient = await createCosmosDBManagementClient(context, this.subscription.subscription); const allAccounts = await uiUtils.listAllIterator(managementClient.databaseAccounts.list()); const accounts = allAccounts.filter((account) => account.kind === 'MongoDB'); @@ -50,6 +51,7 @@ export class AzureMongoRUSubscriptionItem implements TreeElement, TreeElementWit const clusterInfo: ClusterModel = { ...account, resourceGroup: getResourceGroupFromId(resourceId), + dbExperience: CosmosDBMongoRUExperience, } as ClusterModel; return new MongoRUResourceItem(this.subscription.subscription, clusterInfo); @@ -76,4 +78,4 @@ export class AzureMongoRUSubscriptionItem implements TreeElement, TreeElementWit collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, }; } -} \ No newline at end of file +} diff --git a/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts b/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts index e865512d5..f64c9ecc4 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts @@ -9,7 +9,7 @@ import { type NewConnectionWizardContext } from '../../../commands/newConnection import { type GenericResource } from '@azure/arm-resources'; import { type AzureSubscription } from '@microsoft/vscode-azext-azureauth'; -import { AzureContextProperties } from '../../service-azure/AzureDiscoveryProvider'; +import { AzureContextProperties } from '../../api-shared/azure/wizard/AzureContextProperties'; import { extractCredentialsFromRUAccount, getRUClusterInformationFromAzure } from '../utils/ruClusterHelpers'; export class AzureMongoRUExecuteStep extends AzureWizardExecuteStep { @@ -32,7 +32,7 @@ export class AzureMongoRUExecuteStep extends AzureWizardExecuteStep { iconPath = Uri.joinPath( @@ -26,9 +26,7 @@ export class SelectRUClusterStep extends AzureWizardPromptStep { - if ( - context.properties[AzureContextProperties.SelectedSubscription] === undefined - ) { + if (context.properties[AzureContextProperties.SelectedSubscription] === undefined) { throw new Error('SelectedSubscription is not set.'); } @@ -68,4 +66,4 @@ export class SelectRUClusterStep extends AzureWizardPromptStep { From 804c11534068593c32d21e1d4bcc2933d84991b9 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 13:43:53 +0200 Subject: [PATCH 05/13] fix: resolved build errors --- .../service-azure-vm/discovery-wizard/SelectSubscriptionStep.ts | 2 +- src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/service-azure-vm/discovery-wizard/SelectSubscriptionStep.ts b/src/plugins/service-azure-vm/discovery-wizard/SelectSubscriptionStep.ts index 80ec738c9..96eacec1a 100644 --- a/src/plugins/service-azure-vm/discovery-wizard/SelectSubscriptionStep.ts +++ b/src/plugins/service-azure-vm/discovery-wizard/SelectSubscriptionStep.ts @@ -9,7 +9,7 @@ import * as l10n from '@vscode/l10n'; import { Uri, window, type MessageItem, type QuickPickItem } from 'vscode'; import { type NewConnectionWizardContext } from '../../../commands/newConnection/NewConnectionWizardContext'; import { ext } from '../../../extensionVariables'; -import { AzureContextProperties } from '../../service-azure/AzureDiscoveryProvider'; +import { AzureContextProperties } from '../../api-shared/azure/wizard/AzureContextProperties'; export class SelectSubscriptionStep extends AzureWizardPromptStep { iconPath = Uri.joinPath( diff --git a/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts b/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts index f77af641a..f6016bc1f 100644 --- a/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts +++ b/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts @@ -11,7 +11,7 @@ import { Uri, type QuickPickItem } from 'vscode'; import { type NewConnectionWizardContext } from '../../../commands/newConnection/NewConnectionWizardContext'; import { ext } from '../../../extensionVariables'; import { createResourceManagementClient } from '../../../utils/azureClients'; -import { AzureContextProperties } from '../AzureDiscoveryProvider'; +import { AzureContextProperties } from '../../api-shared/azure/wizard/AzureContextProperties'; export class SelectClusterStep extends AzureWizardPromptStep { iconPath = Uri.joinPath( From ce01dcb0f75b938cbc85d69596d61ff7cc7a766d Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 14:01:26 +0200 Subject: [PATCH 06/13] fix: resolved RU credentials discovery issues --- .../AzureMongoRUServiceRootItem.ts | 9 ++-- .../documentdb/MongoRUResourceItem.ts | 17 ++++---- .../AzureMongoRUExecuteStep.ts | 11 +---- .../utils/ruClusterHelpers.ts | 41 +++---------------- 4 files changed, 22 insertions(+), 56 deletions(-) diff --git a/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts index 2579b0baa..5a60b20cf 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-tree/AzureMongoRUServiceRootItem.ts @@ -16,10 +16,11 @@ import { import { type TreeElementWithRetryChildren } from '../../../tree/TreeElementWithRetryChildren'; import { AzureMongoRUSubscriptionItem } from './AzureMongoRUSubscriptionItem'; -export class AzureMongoRUServiceRootItem implements TreeElement, TreeElementWithContextValue, TreeElementWithRetryChildren { +export class AzureMongoRUServiceRootItem + implements TreeElement, TreeElementWithContextValue, TreeElementWithRetryChildren +{ public readonly id: string; - public contextValue: string = - 'enableRefreshCommand;enableFilterCommand;enableLearnMoreCommand;azureMongoRUService'; + public contextValue: string = 'enableRefreshCommand;enableFilterCommand;enableLearnMoreCommand;azureMongoRUService'; constructor( private readonly azureSubscriptionProvider: VSCodeAzureSubscriptionProvider, @@ -90,4 +91,4 @@ export class AzureMongoRUServiceRootItem implements TreeElement, TreeElementWith collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, }; } -} \ No newline at end of file +} diff --git a/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts b/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts index afd65b3e6..4a5c97f40 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts @@ -13,7 +13,7 @@ import { Views } from '../../../../documentdb/Views'; import { ext } from '../../../../extensionVariables'; import { ClusterItemBase, type ClusterCredentials } from '../../../../tree/documentdb/ClusterItemBase'; import { type ClusterModel } from '../../../../tree/documentdb/ClusterModel'; -import { extractCredentialsFromRUAccount, getRUClusterInformationFromAzure } from '../../utils/ruClusterHelpers'; +import { extractCredentialsFromRUAccount } from '../../utils/ruClusterHelpers'; export class MongoRUResourceItem extends ClusterItemBase { iconPath = vscode.Uri.joinPath( @@ -40,15 +40,14 @@ export class MongoRUResourceItem extends ClusterItemBase { context.telemetry.properties.view = Views.DiscoveryView; context.telemetry.properties.discoveryProvider = 'azure-mongo-ru-discovery'; - // Retrieve and validate cluster information (throws if invalid) - const accountInformation = await getRUClusterInformationFromAzure( + const credentials = await extractCredentialsFromRUAccount( context, this.subscription, this.cluster.resourceGroup!, this.cluster.name, ); - return extractCredentialsFromRUAccount(context, accountInformation); + return credentials; }); } @@ -71,9 +70,11 @@ export class MongoRUResourceItem extends ClusterItemBase { // Get credentials for this cluster const credentials = await this.getCredentials(); if (!credentials) { - throw new Error(l10n.t('Unable to retrieve credentials for cluster "{cluster}".', { - cluster: this.cluster.name, - })); + throw new Error( + l10n.t('Unable to retrieve credentials for cluster "{cluster}".', { + cluster: this.cluster.name, + }), + ); } // Cache the credentials for this cluster @@ -119,4 +120,4 @@ export class MongoRUResourceItem extends ClusterItemBase { return result ?? null; } -} \ No newline at end of file +} diff --git a/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts b/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts index f64c9ecc4..63c654ec6 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-wizard/AzureMongoRUExecuteStep.ts @@ -10,7 +10,7 @@ import { type NewConnectionWizardContext } from '../../../commands/newConnection import { type GenericResource } from '@azure/arm-resources'; import { type AzureSubscription } from '@microsoft/vscode-azext-azureauth'; import { AzureContextProperties } from '../../api-shared/azure/wizard/AzureContextProperties'; -import { extractCredentialsFromRUAccount, getRUClusterInformationFromAzure } from '../utils/ruClusterHelpers'; +import { extractCredentialsFromRUAccount } from '../utils/ruClusterHelpers'; export class AzureMongoRUExecuteStep extends AzureWizardExecuteStep { public priority: number = -1; @@ -33,14 +33,7 @@ export class AzureMongoRUExecuteStep extends AzureWizardExecuteStep { - // subscription comes from different azure packages in callers; cast here intentionally - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - const managementClient = await createCosmosDBManagementClient(context, subscription as any); - - const account = await managementClient.databaseAccounts.get(resourceGroup, accountName); - - // Validate that this is a MongoDB RU account - if ((account as { kind?: string }).kind !== 'MongoDB') { - context.telemetry.properties.error = 'invalid-account-kind'; - throw new Error( - l10n.t('Account "{account}" is not a MongoDB account.', { - account: accountName, - }), - ); - } - - return account; -} - -/** - * Extracts credentials from RU account information. - */ -export async function extractCredentialsFromRUAccount( - context: IActionContext, - account: unknown, ): Promise { - // subscription comes from different azure packages in callers; cast here intentionally - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - const managementClient = await createCosmosDBManagementClient(context, account as any); - - const resourceGroup = (account as { id?: string }).id?.split('/')[4]; - const accountName = (account as { name?: string }).name; - if (!resourceGroup || !accountName) { throw new Error(l10n.t('Account information is incomplete.')); } + // subscription comes from different azure packages in callers; cast here intentionally + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + const managementClient = await createCosmosDBManagementClient(context, subscription as any); + const connectionStringsList = await managementClient.databaseAccounts.listConnectionStrings( resourceGroup, accountName, @@ -121,4 +92,4 @@ export async function extractCredentialsFromRUAccount( }; return clusterCredentials; -} \ No newline at end of file +} From 2554ca1870e0c313b9285303a09aff7ca94aea2d Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 14:04:00 +0200 Subject: [PATCH 07/13] fix: 'add to connections' and context menu not showing up for RU account in discovery view --- .../discovery-tree/documentdb/MongoRUResourceItem.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts b/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts index 4a5c97f40..828a7047a 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-tree/documentdb/MongoRUResourceItem.ts @@ -32,7 +32,6 @@ export class MongoRUResourceItem extends ClusterItemBase { cluster: ClusterModel, ) { super(cluster); - this.contextValue = 'mongoRUResource'; } public async getCredentials(): Promise { From 5f2a0d4800480e8a504c3763e44a7ac36259babf Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 14:31:12 +0200 Subject: [PATCH 08/13] feat: promote newly added discovery service (one-time activation) --- .../DiscoveryBranchDataProvider.ts | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/tree/discovery-view/DiscoveryBranchDataProvider.ts b/src/tree/discovery-view/DiscoveryBranchDataProvider.ts index 631f9cf98..db1ae1fa8 100644 --- a/src/tree/discovery-view/DiscoveryBranchDataProvider.ts +++ b/src/tree/discovery-view/DiscoveryBranchDataProvider.ts @@ -86,7 +86,7 @@ export class DiscoveryBranchDataProvider extends BaseExtendedTreeDataProvider { // Reset the set of root items this.currentRootItems = new WeakSet(); @@ -94,6 +94,8 @@ export class DiscoveryBranchDataProvider extends BaseExtendedTreeDataProvider('activeDiscoveryProviderIds', []); @@ -222,4 +224,54 @@ export class DiscoveryBranchDataProvider extends BaseExtendedTreeDataProvider { + const promotionFlagKey = `discoveryProviderPromotionProcessed:${providerId}`; + const promotionAlreadyShown = ext.context.globalState.get(promotionFlagKey, false); + + if (promotionAlreadyShown) { + // Already shown/processed previously — do nothing. + return; + } + + // If there are no registered discovery providers at all, mark the promotion as shown + // and return early. The goal is to only show the promotion to users who have some + // discovery providers active/installed. + const registeredProviders = DiscoveryService.listProviders(); + if (!registeredProviders || registeredProviders.length === 0) { + try { + await ext.context.globalState.update(promotionFlagKey, true); + } catch { + // ignore storage errors for this best-effort write + } + return; + } + + // Only proceed if the provider is actually available + const provider = DiscoveryService.getProvider(providerId); + if (!provider) { + // Provider not registered with DiscoveryService; skip for now. + return; + } + + // Read current active provider IDs + const activeProviderIds = ext.context.globalState.get('activeDiscoveryProviderIds', []); + + // If not present, register it + if (!activeProviderIds.includes(providerId)) { + const updated = [...activeProviderIds, providerId]; + try { + await ext.context.globalState.update('activeDiscoveryProviderIds', updated); + } catch (error) { + console.error(`Failed to update activeDiscoveryProviderIds: ${(error as Error).message}`); + } + } + + // Mark that we've added/shown the promotion for this provider so we don't repeat it + try { + await ext.context.globalState.update(promotionFlagKey, true); + } catch { + // ignore + } + } } From 6011763de5f98877907419ce43ed7c7e7a788d68 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 14:31:16 +0200 Subject: [PATCH 09/13] l10n --- l10n/bundle.l10n.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index d796879f0..a720b8d8d 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -35,7 +35,6 @@ "A connection with the same username and host already exists.": "A connection with the same username and host already exists.", "A new connection will be added to your Connections View.\nDo you want to continue?\n\nNote: You can disable these URL handling confirmations in the exension settings.": "A new connection will be added to your Connections View.\nDo you want to continue?\n\nNote: You can disable these URL handling confirmations in the exension settings.", "A value is required to proceed.": "A value is required to proceed.", - "Account \"{account}\" is not a MongoDB account.": "Account \"{account}\" is not a MongoDB account.", "Account information is incomplete.": "Account information is incomplete.", "Add new document": "Add new document", "Advanced": "Advanced", @@ -271,6 +270,7 @@ "Load More...": "Load More...", "Loading \"{0}\"...": "Loading \"{0}\"...", "Loading cluster details for \"{cluster}\"": "Loading cluster details for \"{cluster}\"", + "Loading clusters…": "Loading clusters…", "Loading Content": "Loading Content", "Loading document {num} of {countUri}": "Loading document {num} of {countUri}", "Loading documents…": "Loading documents…", From 8fe79bf14db0993fffd8b59564402167a5a9794b Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 14:39:11 +0200 Subject: [PATCH 10/13] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../discovery-wizard/SelectRUClusterStep.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts b/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts index ba3ded1d0..0edfd57e1 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts @@ -39,9 +39,10 @@ export class SelectRUClusterStep extends AzureWizardPromptStep account.kind === 'MongoDB'); const promptItems: (QuickPickItem & { id: string })[] = accounts + .filter((account) => account.name) // Filter out accounts without a name .map((account) => ({ id: account.id!, - label: account.name!, + label: account.name, description: account.id, iconPath: this.iconPath, From 1a58ca3c41b05b1e513d5fef5af21777bebbe231 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 16:19:44 +0200 Subject: [PATCH 11/13] fix: build fix around account filtering in service discovery --- .../discovery-wizard/SelectRUClusterStep.ts | 2 +- src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts b/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts index 0edfd57e1..f161c23cb 100644 --- a/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts +++ b/src/plugins/service-azure-mongo-ru/discovery-wizard/SelectRUClusterStep.ts @@ -42,7 +42,7 @@ export class SelectRUClusterStep extends AzureWizardPromptStep account.name) // Filter out accounts without a name .map((account) => ({ id: account.id!, - label: account.name, + label: account.name!, description: account.id, iconPath: this.iconPath, diff --git a/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts b/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts index f6016bc1f..bc0d0b736 100644 --- a/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts +++ b/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts @@ -43,6 +43,7 @@ export class SelectClusterStep extends AzureWizardPromptStep account.name) // Filter out accounts without a name .map((account) => ({ id: account.id!, label: account.name!, From a4ed81eedc30ffc96cc0e65d051feab27a4c3439 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 16:37:52 +0200 Subject: [PATCH 12/13] chore: refactored, renamed azure-discovery to azure-mongo-vcore-discovery --- src/documentdb/ClustersExtension.ts | 2 +- .../azure/tree/AzureSubscriptionItem.ts | 2 +- .../AzureDiscoveryProvider.ts | 2 +- .../discovery-tree/AzureServiceRootItem.ts | 2 +- .../documentdb/DocumentDBResourceItem.ts | 6 +++--- .../discovery-wizard/AzureExecuteStep.ts | 0 .../discovery-wizard/SelectClusterStep.ts | 0 .../utils/clusterHelpers.ts | 0 .../documentdb/VCoreResourceItem.ts | 2 +- .../DiscoveryBranchDataProvider.ts | 18 ++++++++++++++++++ 10 files changed, 26 insertions(+), 8 deletions(-) rename src/plugins/{service-azure => service-azure-mongo-vcore}/AzureDiscoveryProvider.ts (98%) rename src/plugins/{service-azure => service-azure-mongo-vcore}/discovery-tree/AzureServiceRootItem.ts (98%) rename src/plugins/{service-azure => service-azure-mongo-vcore}/discovery-tree/documentdb/DocumentDBResourceItem.ts (99%) rename src/plugins/{service-azure => service-azure-mongo-vcore}/discovery-wizard/AzureExecuteStep.ts (100%) rename src/plugins/{service-azure => service-azure-mongo-vcore}/discovery-wizard/SelectClusterStep.ts (100%) rename src/plugins/{service-azure => service-azure-mongo-vcore}/utils/clusterHelpers.ts (100%) diff --git a/src/documentdb/ClustersExtension.ts b/src/documentdb/ClustersExtension.ts index e5b319a33..955ddc541 100644 --- a/src/documentdb/ClustersExtension.ts +++ b/src/documentdb/ClustersExtension.ts @@ -48,8 +48,8 @@ import { updateCredentials } from '../commands/updateCredentials/updateCredentia import { isVCoreAndRURolloutEnabled } from '../extension'; import { ext } from '../extensionVariables'; import { AzureMongoRUDiscoveryProvider } from '../plugins/service-azure-mongo-ru/AzureMongoRUDiscoveryProvider'; +import { AzureDiscoveryProvider } from '../plugins/service-azure-mongo-vcore/AzureDiscoveryProvider'; import { AzureVMDiscoveryProvider } from '../plugins/service-azure-vm/AzureVMDiscoveryProvider'; -import { AzureDiscoveryProvider } from '../plugins/service-azure/AzureDiscoveryProvider'; import { DiscoveryService } from '../services/discoveryServices'; import { VCoreBranchDataProvider } from '../tree/azure-resources-view/documentdb/VCoreBranchDataProvider'; import { RUBranchDataProvider } from '../tree/azure-resources-view/mongo-ru/RUBranchDataProvider'; diff --git a/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts b/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts index fa3257d4b..45830eb1d 100644 --- a/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts +++ b/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts @@ -14,7 +14,7 @@ import { type TreeElementWithContextValue } from '../../../../tree/TreeElementWi import { type ClusterModel } from '../../../../tree/documentdb/ClusterModel'; import { createResourceManagementClient } from '../../../../utils/azureClients'; import { nonNullProp } from '../../../../utils/nonNull'; -import { DocumentDBResourceItem } from '../../../service-azure/discovery-tree/documentdb/DocumentDBResourceItem'; +import { DocumentDBResourceItem } from '../../../service-azure-mongo-vcore/discovery-tree/documentdb/DocumentDBResourceItem'; export interface AzureSubscriptionModel { subscriptionName: string; diff --git a/src/plugins/service-azure/AzureDiscoveryProvider.ts b/src/plugins/service-azure-mongo-vcore/AzureDiscoveryProvider.ts similarity index 98% rename from src/plugins/service-azure/AzureDiscoveryProvider.ts rename to src/plugins/service-azure-mongo-vcore/AzureDiscoveryProvider.ts index c32706fb5..a58633d62 100644 --- a/src/plugins/service-azure/AzureDiscoveryProvider.ts +++ b/src/plugins/service-azure-mongo-vcore/AzureDiscoveryProvider.ts @@ -18,7 +18,7 @@ import { AzureExecuteStep } from './discovery-wizard/AzureExecuteStep'; import { SelectClusterStep } from './discovery-wizard/SelectClusterStep'; export class AzureDiscoveryProvider extends Disposable implements DiscoveryProvider { - id = 'azure-discovery'; + id = 'azure-mongo-vcore-discovery'; label = l10n.t('Azure Cosmos DB for MongoDB (vCore)'); description = l10n.t('Azure Service Discovery'); iconPath = new ThemeIcon('azure'); diff --git a/src/plugins/service-azure/discovery-tree/AzureServiceRootItem.ts b/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureServiceRootItem.ts similarity index 98% rename from src/plugins/service-azure/discovery-tree/AzureServiceRootItem.ts rename to src/plugins/service-azure-mongo-vcore/discovery-tree/AzureServiceRootItem.ts index fa4203508..ed64361d0 100644 --- a/src/plugins/service-azure/discovery-tree/AzureServiceRootItem.ts +++ b/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureServiceRootItem.ts @@ -25,7 +25,7 @@ export class AzureServiceRootItem implements TreeElement, TreeElementWithContext private readonly azureSubscriptionProvider: VSCodeAzureSubscriptionProvider, public readonly parentId: string, ) { - this.id = `${parentId}/azure-discovery`; + this.id = `${parentId}/azure-mongo-vcore-discovery`; } async getChildren(): Promise { diff --git a/src/plugins/service-azure/discovery-tree/documentdb/DocumentDBResourceItem.ts b/src/plugins/service-azure-mongo-vcore/discovery-tree/documentdb/DocumentDBResourceItem.ts similarity index 99% rename from src/plugins/service-azure/discovery-tree/documentdb/DocumentDBResourceItem.ts rename to src/plugins/service-azure-mongo-vcore/discovery-tree/documentdb/DocumentDBResourceItem.ts index 3346fafd0..8f33604b5 100644 --- a/src/plugins/service-azure/discovery-tree/documentdb/DocumentDBResourceItem.ts +++ b/src/plugins/service-azure-mongo-vcore/discovery-tree/documentdb/DocumentDBResourceItem.ts @@ -49,7 +49,7 @@ export class DocumentDBResourceItem extends ClusterItemBase { public async getCredentials(): Promise { return callWithTelemetryAndErrorHandling('getCredentials', async (context: IActionContext) => { context.telemetry.properties.view = Views.DiscoveryView; - context.telemetry.properties.discoveryProvider = 'azure-discovery'; + context.telemetry.properties.discoveryProvider = 'azure-mongo-vcore-discovery'; // Retrieve and validate cluster information (throws if invalid) const clusterInformation = await getClusterInformationFromAzure( @@ -83,7 +83,7 @@ export class DocumentDBResourceItem extends ClusterItemBase { protected async authenticateAndConnect(): Promise { const result = await callWithTelemetryAndErrorHandling('connect', async (context: IActionContext) => { context.telemetry.properties.view = Views.DiscoveryView; - context.telemetry.properties.discoveryProvider = 'azure-discovery'; + context.telemetry.properties.discoveryProvider = 'azure-mongo-vcore-discovery'; ext.outputChannel.appendLine( l10n.t('Attempting to authenticate with "{cluster}"…', { @@ -189,7 +189,7 @@ export class DocumentDBResourceItem extends ClusterItemBase { // Prompt the user for credentials await callWithTelemetryAndErrorHandling('connect.promptForCredentials', async (context: IActionContext) => { context.telemetry.properties.view = Views.DiscoveryView; - context.telemetry.properties.discoveryProvider = 'azure-discovery'; + context.telemetry.properties.discoveryProvider = 'azure-mongo-vcore-discovery'; context.errorHandling.rethrow = true; context.errorHandling.suppressDisplay = false; diff --git a/src/plugins/service-azure/discovery-wizard/AzureExecuteStep.ts b/src/plugins/service-azure-mongo-vcore/discovery-wizard/AzureExecuteStep.ts similarity index 100% rename from src/plugins/service-azure/discovery-wizard/AzureExecuteStep.ts rename to src/plugins/service-azure-mongo-vcore/discovery-wizard/AzureExecuteStep.ts diff --git a/src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts b/src/plugins/service-azure-mongo-vcore/discovery-wizard/SelectClusterStep.ts similarity index 100% rename from src/plugins/service-azure/discovery-wizard/SelectClusterStep.ts rename to src/plugins/service-azure-mongo-vcore/discovery-wizard/SelectClusterStep.ts diff --git a/src/plugins/service-azure/utils/clusterHelpers.ts b/src/plugins/service-azure-mongo-vcore/utils/clusterHelpers.ts similarity index 100% rename from src/plugins/service-azure/utils/clusterHelpers.ts rename to src/plugins/service-azure-mongo-vcore/utils/clusterHelpers.ts diff --git a/src/tree/azure-resources-view/documentdb/VCoreResourceItem.ts b/src/tree/azure-resources-view/documentdb/VCoreResourceItem.ts index 856fef9a3..6e61c6117 100644 --- a/src/tree/azure-resources-view/documentdb/VCoreResourceItem.ts +++ b/src/tree/azure-resources-view/documentdb/VCoreResourceItem.ts @@ -25,7 +25,7 @@ import { ext } from '../../../extensionVariables'; import { extractCredentialsFromCluster, getClusterInformationFromAzure, -} from '../../../plugins/service-azure/utils/clusterHelpers'; +} from '../../../plugins/service-azure-mongo-vcore/utils/clusterHelpers'; import { nonNullValue } from '../../../utils/nonNull'; import { ClusterItemBase, type ClusterCredentials } from '../../documentdb/ClusterItemBase'; import { type ClusterModel } from '../../documentdb/ClusterModel'; diff --git a/src/tree/discovery-view/DiscoveryBranchDataProvider.ts b/src/tree/discovery-view/DiscoveryBranchDataProvider.ts index db1ae1fa8..f4f1bc9cf 100644 --- a/src/tree/discovery-view/DiscoveryBranchDataProvider.ts +++ b/src/tree/discovery-view/DiscoveryBranchDataProvider.ts @@ -94,6 +94,7 @@ export class DiscoveryBranchDataProvider extends BaseExtendedTreeDataProvider { + try { + const activeProviderIds = ext.context.globalState.get('activeDiscoveryProviderIds', []); + if (activeProviderIds.includes('azure-discovery')) { + { + const updated = ext.context.globalState + .get('activeDiscoveryProviderIds', []) + .filter((id) => id !== 'azure-discovery'); + updated.push('azure-mongo-vcore-discovery'); + await ext.context.globalState.update('activeDiscoveryProviderIds', updated); + } + } + } catch { + // ignore storage errors for this best-effort write + } + } } From 2b596593911d2962a5b9c3d11e9e3fbd485ae67c Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 5 Sep 2025 16:40:24 +0200 Subject: [PATCH 13/13] chore: updated refactoring / moved files. --- .../discovery-tree/AzureServiceRootItem.ts | 2 +- .../discovery-tree}/AzureSubscriptionItem.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) rename src/plugins/{api-shared/azure/tree => service-azure-mongo-vcore/discovery-tree}/AzureSubscriptionItem.ts (83%) diff --git a/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureServiceRootItem.ts b/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureServiceRootItem.ts index ed64361d0..c150280cd 100644 --- a/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureServiceRootItem.ts +++ b/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureServiceRootItem.ts @@ -14,7 +14,7 @@ import { type TreeElementWithContextValue, } from '../../../tree/TreeElementWithContextValue'; import { type TreeElementWithRetryChildren } from '../../../tree/TreeElementWithRetryChildren'; -import { AzureSubscriptionItem } from '../../api-shared/azure/tree/AzureSubscriptionItem'; +import { AzureSubscriptionItem } from './AzureSubscriptionItem'; export class AzureServiceRootItem implements TreeElement, TreeElementWithContextValue, TreeElementWithRetryChildren { public readonly id: string; diff --git a/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts b/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureSubscriptionItem.ts similarity index 83% rename from src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts rename to src/plugins/service-azure-mongo-vcore/discovery-tree/AzureSubscriptionItem.ts index 45830eb1d..e6687fc4b 100644 --- a/src/plugins/api-shared/azure/tree/AzureSubscriptionItem.ts +++ b/src/plugins/service-azure-mongo-vcore/discovery-tree/AzureSubscriptionItem.ts @@ -7,14 +7,14 @@ import { getResourceGroupFromId, uiUtils } from '@microsoft/vscode-azext-azureut import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import * as vscode from 'vscode'; -import { DocumentDBExperience } from '../../../../DocumentDBExperiences'; -import { ext } from '../../../../extensionVariables'; -import { type TreeElement } from '../../../../tree/TreeElement'; -import { type TreeElementWithContextValue } from '../../../../tree/TreeElementWithContextValue'; -import { type ClusterModel } from '../../../../tree/documentdb/ClusterModel'; -import { createResourceManagementClient } from '../../../../utils/azureClients'; -import { nonNullProp } from '../../../../utils/nonNull'; -import { DocumentDBResourceItem } from '../../../service-azure-mongo-vcore/discovery-tree/documentdb/DocumentDBResourceItem'; +import { DocumentDBExperience } from '../../../DocumentDBExperiences'; +import { ext } from '../../../extensionVariables'; +import { type TreeElement } from '../../../tree/TreeElement'; +import { type TreeElementWithContextValue } from '../../../tree/TreeElementWithContextValue'; +import { type ClusterModel } from '../../../tree/documentdb/ClusterModel'; +import { createResourceManagementClient } from '../../../utils/azureClients'; +import { nonNullProp } from '../../../utils/nonNull'; +import { DocumentDBResourceItem } from './documentdb/DocumentDBResourceItem'; export interface AzureSubscriptionModel { subscriptionName: string;