From 2d40e2660dba2d4919c84ce1a42a6204d9b00abf Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 4 Aug 2025 16:12:41 +0200 Subject: [PATCH 1/9] chore: renamed data migration commands internally, always showing the migration option --- package.json | 8 ++++---- .../accessDataMigrationServices.ts} | 2 +- src/documentdb/ClustersExtension.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/commands/{chooseDataMigrationExtension/chooseDataMigrationExtension.ts => accessDataMigrationServices/accessDataMigrationServices.ts} (98%) diff --git a/package.json b/package.json index a5a8007b..9e46ce45 100644 --- a/package.json +++ b/package.json @@ -417,7 +417,7 @@ { "//": "Data Migration", "category": "DocumentDB", - "command": "vscode-documentdb.command.chooseDataMigrationExtension", + "command": "vscode-documentdb.command.accessDataMigrationServices", "title": "Data Migration…" }, { @@ -636,8 +636,8 @@ }, { "//": "[Collection] Data Migration", - "command": "vscode-documentdb.command.chooseDataMigrationExtension", - "when": "view =~ /connectionsView|discoveryView/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i && migrationProvidersAvailable", + "command": "vscode-documentdb.command.accessDataMigrationServices", + "when": "view =~ /connectionsView|discoveryView/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i", "group": "1@2" }, { @@ -748,7 +748,7 @@ "when": "never" }, { - "command": "vscode-documentdb.command.chooseDataMigrationExtension", + "command": "vscode-documentdb.command.accessDataMigrationServices", "when": "never" }, { diff --git a/src/commands/chooseDataMigrationExtension/chooseDataMigrationExtension.ts b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts similarity index 98% rename from src/commands/chooseDataMigrationExtension/chooseDataMigrationExtension.ts rename to src/commands/accessDataMigrationServices/accessDataMigrationServices.ts index 946623a8..90d1e59a 100644 --- a/src/commands/chooseDataMigrationExtension/chooseDataMigrationExtension.ts +++ b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts @@ -11,7 +11,7 @@ import { MigrationService } from '../../services/migrationServices'; import { type ClusterItemBase } from '../../tree/documentdb/ClusterItemBase'; import { openUrl } from '../../utils/openUrl'; -export async function chooseDataMigrationExtension(context: IActionContext, node: ClusterItemBase) { +export async function accessDataMigrationServices(context: IActionContext, node: ClusterItemBase) { const migrationProviders: (QuickPickItem & { id: string })[] = MigrationService.listProviders() // Map to QuickPickItem format .map((provider) => ({ diff --git a/src/documentdb/ClustersExtension.ts b/src/documentdb/ClustersExtension.ts index 167e5602..ff5b0d51 100644 --- a/src/documentdb/ClustersExtension.ts +++ b/src/documentdb/ClustersExtension.ts @@ -17,9 +17,9 @@ import { } from '@microsoft/vscode-azext-utils'; import { AzExtResourceType } from '@microsoft/vscode-azureresources-api'; import * as vscode from 'vscode'; +import { accessDataMigrationServices } from '../commands/accessDataMigrationServices/accessDataMigrationServices'; import { addConnectionFromRegistry } from '../commands/addConnectionFromRegistry/addConnectionFromRegistry'; import { addDiscoveryRegistry } from '../commands/addDiscoveryRegistry/addDiscoveryRegistry'; -import { chooseDataMigrationExtension } from '../commands/chooseDataMigrationExtension/chooseDataMigrationExtension'; import { copyAzureConnectionString } from '../commands/copyConnectionString/copyConnectionString'; import { createCollection } from '../commands/createCollection/createCollection'; import { createAzureDatabase } from '../commands/createDatabase/createDatabase'; @@ -168,8 +168,8 @@ export class ClustersExtension implements vscode.Disposable { }); registerCommandWithTreeNodeUnwrapping( - 'vscode-documentdb.command.chooseDataMigrationExtension', - chooseDataMigrationExtension, + 'vscode-documentdb.command.accessDataMigrationServices', + accessDataMigrationServices, ); //// Registry Commands: From 1ba41dd5796ffd15506445e2bd01e3a900824274 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 4 Aug 2025 16:25:23 +0200 Subject: [PATCH 2/9] feat: added `announcedClients` to the migration config --- api/src/utils/getApi.ts | 16 ++++++++++++++-- package.json | 14 +++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/api/src/utils/getApi.ts b/api/src/utils/getApi.ts index 4c4af96f..226b5d49 100644 --- a/api/src/utils/getApi.ts +++ b/api/src/utils/getApi.ts @@ -9,12 +9,24 @@ import { type DocumentDBExtensionApi } from '../extensionApi'; // The actual extension ID based on the package.json const DOCUMENTDB_EXTENSION_ID = 'ms-azuretools.vscode-documentdb'; +/** + * Interface for announced client configuration + */ +interface AnnouncedClient { + id: string; + name: string; + description: string; + icon: string; + url: string; +} + /** * Interface for the DocumentDB API configuration in package.json */ interface DocumentDBApiConfig { 'x-documentdbApi'?: { - registeredClients?: string[]; + verifiedClients?: string[]; + announcedClients?: AnnouncedClient[]; }; } @@ -59,7 +71,7 @@ export async function getDocumentDBExtensionApi( // Check if the calling extension is whitelisted const packageJson = extension.packageJSON as unknown; const registeredClients = isValidPackageJson(packageJson) - ? packageJson['x-documentdbApi']?.registeredClients + ? packageJson['x-documentdbApi']?.verifiedClients : undefined; if (!registeredClients || !Array.isArray(registeredClients)) { diff --git a/package.json b/package.json index 9e46ce45..ac8e14c2 100644 --- a/package.json +++ b/package.json @@ -912,9 +912,17 @@ ] }, "x-documentdbApi": { - "registeredClients": [ - "vscode-cosmosdb", - "vscode-mongo-migration" + "verifiedClients": [ + "ms-azurecosmosdbtools.vscode-mongo-migration" + ], + "announcedClients": [ + { + "id": "ms-azurecosmosdbtools.vscode-mongo-migration", + "name": "Azure Cosmos DB Migration", + "description": "Assess and migrate your databases to Azure Cosmos DB.", + "icon": "$(extensions)", + "url": "https://marketplace.visualstudio.com/items?itemName=ms-azurecosmosdbtools.vscode-mongo-migration" + } ] } } From d687cdebf7a3a627652b267b5184dea2741d4cb6 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 4 Aug 2025 20:57:52 +0200 Subject: [PATCH 3/9] feat: added support for `announced migration providers` --- api/src/utils/getApi.ts | 12 - package.json | 19 +- .../accessDataMigrationServices.ts | 222 ++++++++++-------- src/services/migrationServices.ts | 27 +++ 4 files changed, 157 insertions(+), 123 deletions(-) diff --git a/api/src/utils/getApi.ts b/api/src/utils/getApi.ts index 226b5d49..eba7b9a9 100644 --- a/api/src/utils/getApi.ts +++ b/api/src/utils/getApi.ts @@ -9,24 +9,12 @@ import { type DocumentDBExtensionApi } from '../extensionApi'; // The actual extension ID based on the package.json const DOCUMENTDB_EXTENSION_ID = 'ms-azuretools.vscode-documentdb'; -/** - * Interface for announced client configuration - */ -interface AnnouncedClient { - id: string; - name: string; - description: string; - icon: string; - url: string; -} - /** * Interface for the DocumentDB API configuration in package.json */ interface DocumentDBApiConfig { 'x-documentdbApi'?: { verifiedClients?: string[]; - announcedClients?: AnnouncedClient[]; }; } diff --git a/package.json b/package.json index ac8e14c2..342d43db 100644 --- a/package.json +++ b/package.json @@ -914,15 +914,14 @@ "x-documentdbApi": { "verifiedClients": [ "ms-azurecosmosdbtools.vscode-mongo-migration" - ], - "announcedClients": [ - { - "id": "ms-azurecosmosdbtools.vscode-mongo-migration", - "name": "Azure Cosmos DB Migration", - "description": "Assess and migrate your databases to Azure Cosmos DB.", - "icon": "$(extensions)", - "url": "https://marketplace.visualstudio.com/items?itemName=ms-azurecosmosdbtools.vscode-mongo-migration" - } ] - } + }, + "x-announcedMigrationProviders": [ + { + "id": "ms-azurecosmosdbtools.vscode-mongo-migration", + "name": "Azure Cosmos DB Migration", + "description": "Assess and migrate your databases to Azure Cosmos DB.", + "url": "https://marketplace.visualstudio.com/items?itemName=ms-azurecosmosdbtools.vscode-mongo-migration" + } + ] } diff --git a/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts index 90d1e59a..a66e63de 100644 --- a/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts +++ b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts @@ -5,14 +5,16 @@ import { nonNullValue, type IActionContext } from '@microsoft/vscode-azext-utils'; import * as l10n from '@vscode/l10n'; -import { QuickPickItemKind, type QuickPickItem } from 'vscode'; +import { commands, QuickPickItemKind, type QuickPickItem } from 'vscode'; import { CredentialCache } from '../../documentdb/CredentialCache'; import { MigrationService } from '../../services/migrationServices'; import { type ClusterItemBase } from '../../tree/documentdb/ClusterItemBase'; import { openUrl } from '../../utils/openUrl'; +const ANNOUNCED_PROVIDER_PREFIX = 'announced-provider'; + export async function accessDataMigrationServices(context: IActionContext, node: ClusterItemBase) { - const migrationProviders: (QuickPickItem & { id: string })[] = MigrationService.listProviders() + const installedProviders: (QuickPickItem & { id: string })[] = MigrationService.listProviders() // Map to QuickPickItem format .map((provider) => ({ id: provider.id, @@ -20,7 +22,22 @@ export async function accessDataMigrationServices(context: IActionContext, node: detail: provider.description, iconPath: provider.iconPath, - group: 'Migration Providers', + group: 'Installed Providers', + alwaysShow: true, + })) + // Sort alphabetically + .sort((a, b) => a.label.localeCompare(b.label)); + + const announcedProviers: (QuickPickItem & { id: string })[] = MigrationService.listAnnouncedProviders(true) + // Map to QuickPickItem format + .map((provider) => ({ + id: `${ANNOUNCED_PROVIDER_PREFIX}-${provider.id}`, // please note, the prefix is a magic string here, and needed to correctly support vs code marketplace integration + label: `$(extensions) ${provider.name}`, + detail: `Open the VS Code Marketplace to learn more about "${provider.name}"`, + url: provider.url, + + marketplaceId: provider.id, + group: 'Visit Marketplace', alwaysShow: true, })) // Sort alphabetically @@ -42,13 +59,14 @@ export async function accessDataMigrationServices(context: IActionContext, node: label: l10n.t('Learn more…'), detail: l10n.t('Learn more about DocumentDB and MongoDB migrations.'), - learnMoreUrl: 'https://aka.ms/vscode-documentdb-migration-support', - alwaysShow: true, + url: 'https://aka.ms/vscode-documentdb-migration-support', + group: 'Learn More', + alwaysShow: true, }, ]; - const selectedItem = await context.ui.showQuickPick([...migrationProviders, ...commonItems], { + const selectedItem = await context.ui.showQuickPick([...installedProviders, ...announcedProviers, ...commonItems], { enableGrouping: true, placeHolder: l10n.t('Choose the data migration provider…'), stepName: 'selectMigrationProvider', @@ -59,8 +77,15 @@ export async function accessDataMigrationServices(context: IActionContext, node: if (selectedItem.id === 'learnMore') { context.telemetry.properties.migrationLearnMore = 'true'; - if ('learnMoreUrl' in selectedItem && selectedItem.learnMoreUrl) { - await openUrl(selectedItem.learnMoreUrl); + if ('url' in selectedItem && selectedItem.url) { + await openUrl(selectedItem.url); + } + } + + if (selectedItem.id?.startsWith(ANNOUNCED_PROVIDER_PREFIX)) { + context.telemetry.properties.migrationAddProvider = 'true'; + if ('marketplaceId' in selectedItem && selectedItem.marketplaceId) { + commands.executeCommand('extension.open', selectedItem.marketplaceId); } } @@ -70,109 +95,104 @@ export async function accessDataMigrationServices(context: IActionContext, node: // return; // } - if (migrationProviders.some((provider) => provider.id === selectedItem.id)) { + if (installedProviders.some((provider) => provider.id === selectedItem.id)) { const selectedProvider = MigrationService.getProvider(nonNullValue(selectedItem.id, 'selectedItem.id')); - if (selectedProvider) { - context.telemetry.properties.migrationProvider = selectedProvider.id; + if (!selectedProvider) { + return; + } - // Check if the selected provider requires authentication for the default action - if (selectedProvider.requiresAuthentication) { + context.telemetry.properties.migrationProvider = selectedProvider.id; + + // Check if the selected provider requires authentication for the default action + if (selectedProvider.requiresAuthentication) { + const authenticated = await ensureAuthentication(context, node); + if (!authenticated) { + void context.ui.showWarningMessage( + l10n.t('Authentication is required to use this migration provider.'), + { + modal: true, + detail: l10n.t('Please authenticate first by expanding the tree item of the selected cluster.'), + }, + ); + return; + } + } + + try { + // Construct the options object with available context + const options = { + connectionString: await node.getConnectionString(), + extendedProperties: { + clusterId: node.cluster.id, + }, + }; + + // Get available actions from the provider + const availableActions = await selectedProvider.getAvailableActions(options); + + if (availableActions.length === 0) { + // No actions available, execute default action + return selectedProvider.executeAction(options); + } + + // Extend actions with Learn More option if provider has a learn more URL + const extendedActions: (QuickPickItem & { + id: string; + url?: string; + requiresAuthentication?: boolean; + })[] = [...availableActions]; + + const url = selectedProvider.getLearnMoreUrl?.(); + + if (url) { + extendedActions.push( + { id: 'separator', label: '', kind: QuickPickItemKind.Separator }, + { + id: 'learnMore', + label: l10n.t('Learn more…'), + detail: l10n.t('Learn more about {0}.', selectedProvider.label), + url, + alwaysShow: true, + }, + ); + } + + // Show action picker to user + const selectedAction = await context.ui.showQuickPick(extendedActions, { + placeHolder: l10n.t('Choose the migration action…'), + stepName: 'selectMigrationAction', + suppressPersistence: true, + }); + + if (selectedAction.id === 'learnMore') { + context.telemetry.properties.migrationLearnMore = 'true'; + if (selectedAction.url) { + await openUrl(selectedAction.url); + } + return; + } + + // Check if selected action requires authentication + if (selectedAction.requiresAuthentication) { const authenticated = await ensureAuthentication(context, node); if (!authenticated) { - void context.ui.showWarningMessage( - l10n.t('Authentication is required to use this migration provider.'), - { - modal: true, - detail: l10n.t( - 'Please authenticate first by expanding the tree item of the selected cluster.', - ), - }, - ); + void context.ui.showWarningMessage(l10n.t('Authentication is required to run this action.'), { + modal: true, + detail: l10n.t('Please authenticate first by expanding the tree item of the selected cluster.'), + }); return; } } - try { - // Construct the options object with available context - const options = { - connectionString: await node.getConnectionString(), - extendedProperties: { - clusterId: node.cluster.id, - }, - }; - - // Get available actions from the provider - const availableActions = await selectedProvider.getAvailableActions(options); - - if (availableActions.length === 0) { - // No actions available, execute default action - await selectedProvider.executeAction(options); - } else { - // Extend actions with Learn More option if provider has a learn more URL - const extendedActions: (QuickPickItem & { - id: string; - learnMoreUrl?: string; - requiresAuthentication?: boolean; - })[] = [...availableActions]; - - const learnMoreUrl = selectedProvider.getLearnMoreUrl?.(); - - if (learnMoreUrl) { - extendedActions.push( - { id: 'separator', label: '', kind: QuickPickItemKind.Separator }, - { - id: 'learnMore', - label: l10n.t('Learn more…'), - detail: l10n.t('Learn more about {0}.', selectedProvider.label), - learnMoreUrl, - alwaysShow: true, - }, - ); - } - - // Show action picker to user - const selectedAction = await context.ui.showQuickPick(extendedActions, { - placeHolder: l10n.t('Choose the migration action…'), - stepName: 'selectMigrationAction', - suppressPersistence: true, - }); + context.telemetry.properties.migrationAction = selectedAction.id; - if (selectedAction.id === 'learnMore') { - context.telemetry.properties.migrationLearnMore = 'true'; - if (selectedAction.learnMoreUrl) { - await openUrl(selectedAction.learnMoreUrl); - } - return; - } - - // Check if selected action requires authentication - if (selectedAction.requiresAuthentication) { - const authenticated = await ensureAuthentication(context, node); - if (!authenticated) { - void context.ui.showWarningMessage( - l10n.t('Authentication is required to run this action.'), - { - modal: true, - detail: l10n.t( - 'Please authenticate first by expanding the tree item of the selected cluster.', - ), - }, - ); - return; - } - } - - context.telemetry.properties.migrationAction = selectedAction.id; - - // Execute the selected action - await selectedProvider.executeAction(options, selectedAction.id); - } - } catch (error) { - // Log the error and re-throw to be handled by the caller - console.error('Error during migration provider execution:', error); - throw error; - } + // Execute the selected action + await selectedProvider.executeAction(options, selectedAction.id); + } catch (error) { + // Log the error and re-throw to be handled by the caller + console.error('Error during migration provider execution:', error); + throw error; } } } diff --git a/src/services/migrationServices.ts b/src/services/migrationServices.ts index b437b073..6cdc862b 100644 --- a/src/services/migrationServices.ts +++ b/src/services/migrationServices.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { ext } from '../extensionVariables'; /** * Represents basic information about a migration provider. @@ -56,6 +57,16 @@ export interface ActionsOptions { extendedProperties?: { [key: string]: string | undefined }; } +/** + * Interface for announced provider configuration + */ +export interface AnnouncedMigrationProvider { + id: string; + name: string; + description: string; + url: string; +} + /** * Private implementation of MigrationService that manages migration providers * for migration-related functionality. @@ -94,6 +105,22 @@ class MigrationServiceImpl { return providers; } + public listAnnouncedProviders(hideInstalled: boolean = true): AnnouncedMigrationProvider[] { + const packageJson = ext.context.extension.packageJSON as unknown; + if (!packageJson || !packageJson['x-announcedMigrationProviders']) { + return []; + } + + const announcedProviders = packageJson['x-announcedMigrationProviders'] as AnnouncedMigrationProvider[]; + + if (hideInstalled) { + // Filter out providers that are already registered + return announcedProviders.filter((provider) => !this.migrationProviders.has(provider.id)); + } + + return announcedProviders; + } + /** * Updates the VS Code context to reflect the current state of migration providers. * Sets 'migrationProvidersAvailable' to true when providers are registered. From 2fffbdce1e9b340c22fdb61aca3d69abef469ae4 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 5 Aug 2025 12:20:30 +0200 Subject: [PATCH 4/9] fix: improved client extension registration via getApi improvements --- api/src/utils/getApi.ts | 27 +++++++++++++++++++++------ src/extension.ts | 15 +++++++++++++++ src/services/migrationServices.ts | 15 ++++++++++++++- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/api/src/utils/getApi.ts b/api/src/utils/getApi.ts index eba7b9a9..2600ff9d 100644 --- a/api/src/utils/getApi.ts +++ b/api/src/utils/getApi.ts @@ -44,11 +44,11 @@ function isValidPackageJson(packageJson: unknown): packageJson is DocumentDBApiC * ``` */ export async function getDocumentDBExtensionApi( - _context: vscode.ExtensionContext, + context: vscode.ExtensionContext, apiVersionRange: string, ): Promise { // Get the calling extension's ID from the context - const callingExtensionId = _context.extension.id; + const callingExtensionId = context.extension.id; // Get the DocumentDB extension to access its package.json configuration const extension = vscode.extensions.getExtension(DOCUMENTDB_EXTENSION_ID); @@ -58,15 +58,15 @@ export async function getDocumentDBExtensionApi( // Check if the calling extension is whitelisted const packageJson = extension.packageJSON as unknown; - const registeredClients = isValidPackageJson(packageJson) + const verifiedClients = isValidPackageJson(packageJson) ? packageJson['x-documentdbApi']?.verifiedClients : undefined; - if (!registeredClients || !Array.isArray(registeredClients)) { - throw new Error(`DocumentDB for VS Code API configuration is invalid. No registered clients found.`); + if (!verifiedClients || !Array.isArray(verifiedClients)) { + throw new Error(`DocumentDB for VS Code API configuration is invalid. No verified client list found.`); } - if (!registeredClients.includes(callingExtensionId)) { + if (!verifiedClients.includes(callingExtensionId)) { throw new Error( `Extension '${callingExtensionId}' is not authorized to use the DocumentDB for VS Code API. ` + `This is an experimental API with whitelisted access. ` + @@ -89,5 +89,20 @@ export async function getDocumentDBExtensionApi( console.warn(`API version mismatch. Expected ${apiVersionRange}, got ${api.apiVersion}`); } + try { + // going via an "internal" command here to avoid making the registraction function public + const success = await vscode.commands.executeCommand( + 'vscode-documentdb.command.internal.api.registerClientExtension', + context.extension.id, + ); + + if (success !== true) { + console.warn(`Client registration may have failed for "${callingExtensionId}"`); + } + } catch (error) { + // Log error but don't fail API retrieval + console.warn(`Failed to register client "${callingExtensionId}": ${error}`); + } + return api; } diff --git a/src/extension.ts b/src/extension.ts index aea1dc8d..3b26716e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -88,6 +88,21 @@ export async function activateInternal( }, }; + context.subscriptions.push( + vscode.commands.registerCommand( + 'vscode-documentdb.command.internal.api.registerClientExtension', + (clientExtensionId: string) => { + try { + MigrationService.registerClientExtension(clientExtensionId); + return true; + } catch (error) { + console.error('Failed to register client:', error); + return false; + } + }, + ), + ); + // Return both the DocumentDB API and Azure Extension API return { ...documentDBApi, diff --git a/src/services/migrationServices.ts b/src/services/migrationServices.ts index 6cdc862b..56e9a9de 100644 --- a/src/services/migrationServices.ts +++ b/src/services/migrationServices.ts @@ -78,6 +78,19 @@ export interface AnnouncedMigrationProvider { */ class MigrationServiceImpl { private migrationProviders: Map = new Map(); + private registeredClientExtensions: Set = new Set(); + + /** + * Registers the calling extension as a client of the DocumentDB API. + * This is used to correctly handle announced providers and avoiding "double-announcements". + * @param clientContext The context of the calling extension. + */ + public registerClientExtension(clientExtensionId: string): void { + this.registeredClientExtensions.add(clientExtensionId); + + // Note: we don't support "unregistering" clients, this would require + // detecting when the extension is deactivated and removing it from the set... + } public registerProvider(provider: MigrationProvider): void { this.migrationProviders.set(provider.id, provider); @@ -115,7 +128,7 @@ class MigrationServiceImpl { if (hideInstalled) { // Filter out providers that are already registered - return announcedProviders.filter((provider) => !this.migrationProviders.has(provider.id)); + return announcedProviders.filter((provider) => !this.registeredClientExtensions.has(provider.id)); } return announcedProviders; From 63311a5c59c3e59957a4f1aeaddba8540304f44c Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 5 Aug 2025 12:49:24 +0200 Subject: [PATCH 5/9] updated migration api version --- api/package-lock.json | 4 ++-- api/package.json | 2 +- l10n/bundle.l10n.json | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index b6e21413..a1866d67 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-documentdb-api-experimental-beta", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-documentdb-api-experimental-beta", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "devDependencies": { "@microsoft/api-extractor": "^7.38.0", diff --git a/api/package.json b/api/package.json index 64e5757d..3404c598 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "vscode-documentdb-api-experimental-beta", - "version": "0.2.0", + "version": "0.3.0", "description": "Extension API for VS Code DocumentDB extension (preview)", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 09713b7e..1828031d 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -43,6 +43,7 @@ "An error has occurred. Check output window for more details.": "An error has occurred. Check output window for more details.", "An item with id \"{0}\" already exists for workspace \"{1}\".": "An item with id \"{0}\" already exists for workspace \"{1}\".", "API version \"{0}\" for extension id \"{1}\" is no longer supported. Minimum version is \"{2}\".": "API version \"{0}\" for extension id \"{1}\" is no longer supported. Minimum version is \"{2}\".", + "API: Registered new client extension: \"{clientExtensionId}\"": "API: Registered new client extension: \"{clientExtensionId}\"", "API: Registered new migration provider: \"{providerId}\" - \"{providerLabel}\"": "API: Registered new migration provider: \"{providerId}\" - \"{providerLabel}\"", "Are you sure?": "Are you sure?", "Attempting to authenticate with \"{cluster}\"…": "Attempting to authenticate with \"{cluster}\"…", From ae60948ab8511f600ebffb03ffe7840d3dbfa2a8 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 5 Aug 2025 12:53:07 +0200 Subject: [PATCH 6/9] Updated api version --- src/extension.ts | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 3b26716e..fb1c7091 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,6 +10,7 @@ import { callWithTelemetryAndErrorHandling, createApiProvider, createAzExtLogOutputChannel, + registerCommand, registerErrorHandler, registerUIExtensionVariables, TreeElementStateManager, @@ -73,7 +74,7 @@ export async function activateInternal( // Create the DocumentDB Extension API const documentDBApi: DocumentDBExtensionApi = { - apiVersion: '0.2.0', + apiVersion: '0.3.0', migration: { registerProvider: (provider) => { MigrationService.registerProvider(provider); @@ -88,19 +89,22 @@ export async function activateInternal( }, }; - context.subscriptions.push( - vscode.commands.registerCommand( - 'vscode-documentdb.command.internal.api.registerClientExtension', - (clientExtensionId: string) => { - try { - MigrationService.registerClientExtension(clientExtensionId); - return true; - } catch (error) { - console.error('Failed to register client:', error); - return false; - } - }, - ), + registerCommand( + 'vscode-documentdb.command.internal.api.registerClientExtension', + (_context: IActionContext, clientExtensionId: string) => { + try { + MigrationService.registerClientExtension(clientExtensionId); + ext.outputChannel.appendLine( + vscode.l10n.t('API: Registered new client extension: "{clientExtensionId}"', { + clientExtensionId, + }), + ); + return true; + } catch (error) { + console.error('Failed to register client:', error); + return false; + } + }, ); // Return both the DocumentDB API and Azure Extension API From 6f16915bef096ecd23606b4e76b1fe053c8bf615 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 5 Aug 2025 13:15:50 +0200 Subject: [PATCH 7/9] fix: workaround for v0.2.0 API users --- src/services/migrationServices.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/services/migrationServices.ts b/src/services/migrationServices.ts index 56e9a9de..fe4adf09 100644 --- a/src/services/migrationServices.ts +++ b/src/services/migrationServices.ts @@ -128,7 +128,20 @@ class MigrationServiceImpl { if (hideInstalled) { // Filter out providers that are already registered - return announcedProviders.filter((provider) => !this.registeredClientExtensions.has(provider.id)); + const filteredList = announcedProviders.filter( + (provider) => !this.registeredClientExtensions.has(provider.id), + ); + + // Hardcoded fix for an older version of the migration extension that used a generic provider ID. + // If the old provider is detected, we hide the announcement to avoid duplicates. + const oldMigrationProvider = this.migrationProviders.get('one-action-provider'); + if (oldMigrationProvider?.label === 'Pre-Migration Assessment for Azure Cosmos DB') { + return filteredList.filter( + (provider) => provider.id !== 'ms-azurecosmosdbtools.vscode-mongo-migration', + ); + } + + return filteredList; } return announcedProviders; From 8918749a286a63150771529ba0e7b110b54505c4 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 5 Aug 2025 13:23:01 +0200 Subject: [PATCH 8/9] fix: typos detected by copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- api/src/utils/getApi.ts | 2 +- .../accessDataMigrationServices.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/utils/getApi.ts b/api/src/utils/getApi.ts index 2600ff9d..22ff7bec 100644 --- a/api/src/utils/getApi.ts +++ b/api/src/utils/getApi.ts @@ -90,7 +90,7 @@ export async function getDocumentDBExtensionApi( } try { - // going via an "internal" command here to avoid making the registraction function public + // going via an "internal" command here to avoid making the registration function public const success = await vscode.commands.executeCommand( 'vscode-documentdb.command.internal.api.registerClientExtension', context.extension.id, diff --git a/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts index a66e63de..3685fcd2 100644 --- a/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts +++ b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts @@ -28,7 +28,7 @@ export async function accessDataMigrationServices(context: IActionContext, node: // Sort alphabetically .sort((a, b) => a.label.localeCompare(b.label)); - const announcedProviers: (QuickPickItem & { id: string })[] = MigrationService.listAnnouncedProviders(true) + const announcedProviders: (QuickPickItem & { id: string })[] = MigrationService.listAnnouncedProviders(true) // Map to QuickPickItem format .map((provider) => ({ id: `${ANNOUNCED_PROVIDER_PREFIX}-${provider.id}`, // please note, the prefix is a magic string here, and needed to correctly support vs code marketplace integration @@ -66,7 +66,7 @@ export async function accessDataMigrationServices(context: IActionContext, node: }, ]; - const selectedItem = await context.ui.showQuickPick([...installedProviders, ...announcedProviers, ...commonItems], { + const selectedItem = await context.ui.showQuickPick([...installedProviders, ...announcedProviders, ...commonItems], { enableGrouping: true, placeHolder: l10n.t('Choose the data migration provider…'), stepName: 'selectMigrationProvider', From 00db884b23a483773f72905fbffc8322ddd484fe Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 5 Aug 2025 13:26:11 +0200 Subject: [PATCH 9/9] prettier-fix --- .../accessDataMigrationServices.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts index 3685fcd2..daff8c36 100644 --- a/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts +++ b/src/commands/accessDataMigrationServices/accessDataMigrationServices.ts @@ -66,12 +66,15 @@ export async function accessDataMigrationServices(context: IActionContext, node: }, ]; - const selectedItem = await context.ui.showQuickPick([...installedProviders, ...announcedProviders, ...commonItems], { - enableGrouping: true, - placeHolder: l10n.t('Choose the data migration provider…'), - stepName: 'selectMigrationProvider', - suppressPersistence: true, - }); + const selectedItem = await context.ui.showQuickPick( + [...installedProviders, ...announcedProviders, ...commonItems], + { + enableGrouping: true, + placeHolder: l10n.t('Choose the data migration provider…'), + stepName: 'selectMigrationProvider', + suppressPersistence: true, + }, + ); context.telemetry.properties.connectionMode = selectedItem.id;