From 40af04ce5e4d85b0526098e960ed1541743ba620 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 21 Nov 2025 12:10:56 +0100 Subject: [PATCH 01/11] chore: removed unused code --- src/commands/removeConnection/removeConnection.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index 493187c35..b2cae1ae8 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -12,14 +12,6 @@ import { type DocumentDBClusterItem } from '../../tree/connections-view/Document import { getConfirmationAsInSettings } from '../../utils/dialogs/getConfirmation'; import { showConfirmationAsInSettings } from '../../utils/dialogs/showConfirmation'; -export async function removeAzureConnection(context: IActionContext, node: DocumentDBClusterItem): Promise { - if (!node) { - throw new Error(l10n.t('No node selected.')); - } - - await removeConnection(context, node); -} - export async function removeConnection(context: IActionContext, node: DocumentDBClusterItem): Promise { context.telemetry.properties.experience = node.experience.api; const confirmed = await getConfirmationAsInSettings( @@ -40,7 +32,10 @@ export async function removeConnection(context: IActionContext, node: DocumentDB if ((node as DocumentDBClusterItem).cluster.emulatorConfiguration?.isEmulator) { await ConnectionStorageService.delete(ConnectionType.Emulators, node.storageId); } else { + const start = performance.now(); await ConnectionStorageService.delete(ConnectionType.Clusters, node.storageId); + const end = performance.now(); + console.debug(`Time taken to delete connection from storage: ${end - start} ms`); } }); From 3f7eaee8642102ea74c19bf4571f4b81264f54f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:11:12 +0000 Subject: [PATCH 02/11] Initial plan From 6a08a2c190133007b27e66f828a0b13d7a53e2f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:24:20 +0000 Subject: [PATCH 03/11] Implement resilient multi-select connection deletion with logging - Add support for multi-selection (node and nodes parameters) - Remove performance timing logs (performance.now() calls) - Implement resilient deletion loop that continues on failure - Add detailed logging to ext.outputChannel for each deletion - Add summary messages for multi-deletion operations - Add telemetry measurement for number of removed connections - Update confirmation logic for single vs. multiple deletions - Update localization files with new strings Co-authored-by: tnaum-ms <171359267+tnaum-ms@users.noreply.github.com> --- l10n/bundle.l10n.json | 5 + .../removeConnection/removeConnection.ts | 94 +++++++++++++++---- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 7e7e9b43d..444db5981 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -217,6 +217,7 @@ "Delete": "Delete", "Delete \"{connectionName}\"?": "Delete \"{connectionName}\"?", "Delete \"{nodeName}\"?": "Delete \"{nodeName}\"?", + "Delete {count} connections?": "Delete {count} connections?", "Delete {count} documents?": "Delete {count} documents?", "Delete collection \"{collectionId}\" and its contents?": "Delete collection \"{collectionId}\" and its contents?", "Delete database \"{databaseId}\" and its contents?": "Delete database \"{databaseId}\" and its contents?", @@ -352,6 +353,7 @@ "Failed to parse secrets for key {0}:": "Failed to parse secrets for key {0}:", "Failed to parse the response from the language model. LLM output:\n{output}": "Failed to parse the response from the language model. LLM output:\n{output}", "Failed to process URI: {0}": "Failed to process URI: {0}", + "Failed to remove connection \"{connectionName}\": {error}": "Failed to remove connection \"{connectionName}\": {error}", "Failed to rename the connection.": "Failed to rename the connection.", "Failed to retrieve Azure accounts: {0}": "Failed to retrieve Azure accounts: {0}", "Failed to save credentials for \"{cluster}\".": "Failed to save credentials for \"{cluster}\".", @@ -595,6 +597,7 @@ "Reload original document from the database": "Reload original document from the database", "Reload Window": "Reload Window", "Remind Me Later": "Remind Me Later", + "Removed {successCount} of {total} connections. {failureCount} failed.": "Removed {successCount} of {total} connections. {failureCount} failed.", "Rename Connection": "Rename Connection", "Report a Bug": "Report a Bug", "report an issue": "report an issue", @@ -666,6 +669,8 @@ "Successfully created resource group \"{0}\".": "Successfully created resource group \"{0}\".", "Successfully created storage account \"{0}\".": "Successfully created storage account \"{0}\".", "Successfully created user assigned identity \"{0}\".": "Successfully created user assigned identity \"{0}\".", + "Successfully removed {count} connections.": "Successfully removed {count} connections.", + "Successfully removed connection \"{connectionName}\".": "Successfully removed connection \"{connectionName}\".", "Suggest a Feature": "Suggest a Feature", "Sure!": "Sure!", "Switch to the new \"Connections View\"…": "Switch to the new \"Connections View\"…", diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index b2cae1ae8..0b22dc4d0 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -12,13 +12,32 @@ import { type DocumentDBClusterItem } from '../../tree/connections-view/Document import { getConfirmationAsInSettings } from '../../utils/dialogs/getConfirmation'; import { showConfirmationAsInSettings } from '../../utils/dialogs/showConfirmation'; -export async function removeConnection(context: IActionContext, node: DocumentDBClusterItem): Promise { - context.telemetry.properties.experience = node.experience.api; +export async function removeConnection( + context: IActionContext, + node?: DocumentDBClusterItem, + nodes?: DocumentDBClusterItem[], +): Promise { + // Determine the list of connections to delete + const connectionsToDelete: DocumentDBClusterItem[] = nodes && nodes.length > 0 ? nodes : node ? [node] : []; + + if (connectionsToDelete.length === 0) { + return; + } + + // Set telemetry for the first node + context.telemetry.properties.experience = connectionsToDelete[0].experience.api; + context.telemetry.measurements.removedConnections = connectionsToDelete.length; + + // Confirmation logic - different messages for single vs. multiple deletions const confirmed = await getConfirmationAsInSettings( l10n.t('Are you sure?'), - l10n.t('Delete "{connectionName}"?', { connectionName: node.cluster.name }) + - '\n' + - l10n.t('This cannot be undone.'), + connectionsToDelete.length === 1 + ? l10n.t('Delete "{connectionName}"?', { connectionName: connectionsToDelete[0].cluster.name }) + + '\n' + + l10n.t('This cannot be undone.') + : l10n.t('Delete {count} connections?', { count: connectionsToDelete.length }) + + '\n' + + l10n.t('This cannot be undone.'), 'delete', ); @@ -26,23 +45,60 @@ export async function removeConnection(context: IActionContext, node: DocumentDB throw new UserCancelledError(); } - // continue with deletion + // Resilient deletion loop - continue on failure + let successCount = 0; + let failureCount = 0; - await ext.state.showDeleting(node.id, async () => { - if ((node as DocumentDBClusterItem).cluster.emulatorConfiguration?.isEmulator) { - await ConnectionStorageService.delete(ConnectionType.Emulators, node.storageId); - } else { - const start = performance.now(); - await ConnectionStorageService.delete(ConnectionType.Clusters, node.storageId); - const end = performance.now(); - console.debug(`Time taken to delete connection from storage: ${end - start} ms`); - } - }); + for (const connection of connectionsToDelete) { + try { + await ext.state.showDeleting(connection.id, async () => { + if (connection.cluster.emulatorConfiguration?.isEmulator) { + await ConnectionStorageService.delete(ConnectionType.Emulators, connection.storageId); + } else { + await ConnectionStorageService.delete(ConnectionType.Clusters, connection.storageId); + } + }); - // delete cached credentials from memory - CredentialCache.deleteCredentials(node.id); + // delete cached credentials from memory + CredentialCache.deleteCredentials(connection.id); + // Log success + ext.outputChannel.info( + l10n.t('Successfully removed connection "{connectionName}".', { + connectionName: connection.cluster.name, + }), + ); + successCount++; + } catch (error) { + // Log error and continue with next connection + ext.outputChannel.error( + l10n.t('Failed to remove connection "{connectionName}": {error}', { + connectionName: connection.cluster.name, + error: error instanceof Error ? error.message : String(error), + }), + ); + failureCount++; + } + } + + // Refresh the tree view ext.connectionsBranchDataProvider.refresh(); - showConfirmationAsInSettings(l10n.t('The selected connection has been removed.')); + // Show summary message + if (connectionsToDelete.length === 1) { + if (successCount === 1) { + showConfirmationAsInSettings(l10n.t('The selected connection has been removed.')); + } + } else { + // Show summary for multiple deletions + const summaryMessage = + failureCount === 0 + ? l10n.t('Successfully removed {count} connections.', { count: successCount }) + : l10n.t('Removed {successCount} of {total} connections. {failureCount} failed.', { + successCount, + total: connectionsToDelete.length, + failureCount, + }); + showConfirmationAsInSettings(summaryMessage); + } } From dba5df038f48595148d70eaa82fdd186c1aa3a7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:26:57 +0000 Subject: [PATCH 04/11] Address code review feedback: improve readability and error handling - Replace complex ternary chain with explicit if-else statements - Add error handling for single connection deletion failure - Update localization with new error message string Co-authored-by: tnaum-ms <171359267+tnaum-ms@users.noreply.github.com> --- l10n/bundle.l10n.json | 1 + .../removeConnection/removeConnection.ts | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 444db5981..ee8daa6ca 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -354,6 +354,7 @@ "Failed to parse the response from the language model. LLM output:\n{output}": "Failed to parse the response from the language model. LLM output:\n{output}", "Failed to process URI: {0}": "Failed to process URI: {0}", "Failed to remove connection \"{connectionName}\": {error}": "Failed to remove connection \"{connectionName}\": {error}", + "Failed to remove connection \"{connectionName}\".": "Failed to remove connection \"{connectionName}\".", "Failed to rename the connection.": "Failed to rename the connection.", "Failed to retrieve Azure accounts: {0}": "Failed to retrieve Azure accounts: {0}", "Failed to save credentials for \"{cluster}\".": "Failed to save credentials for \"{cluster}\".", diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index 0b22dc4d0..c16e32098 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -18,7 +18,14 @@ export async function removeConnection( nodes?: DocumentDBClusterItem[], ): Promise { // Determine the list of connections to delete - const connectionsToDelete: DocumentDBClusterItem[] = nodes && nodes.length > 0 ? nodes : node ? [node] : []; + let connectionsToDelete: DocumentDBClusterItem[]; + if (nodes && nodes.length > 0) { + connectionsToDelete = nodes; + } else if (node) { + connectionsToDelete = [node]; + } else { + connectionsToDelete = []; + } if (connectionsToDelete.length === 0) { return; @@ -86,8 +93,16 @@ export async function removeConnection( // Show summary message if (connectionsToDelete.length === 1) { + // For single connection, show success or error message if (successCount === 1) { showConfirmationAsInSettings(l10n.t('The selected connection has been removed.')); + } else { + // Error is already logged to outputChannel, so throw error to show notification + throw new Error( + l10n.t('Failed to remove connection "{connectionName}".', { + connectionName: connectionsToDelete[0].cluster.name, + }), + ); } } else { // Show summary for multiple deletions From 9bbfea00e9e55a7a462bbd72003796cca0eac8ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:32:28 +0000 Subject: [PATCH 05/11] Add telemetry for both target and actual deletion counts - Renamed removedConnections to connectionsToDelete for clarity - Added connectionsDeleted to track successfully deleted connections - Now collects both metrics: connections targeted for deletion and actual deletions Co-authored-by: tnaum-ms <171359267+tnaum-ms@users.noreply.github.com> --- src/commands/removeConnection/removeConnection.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index c16e32098..8f403c91a 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -33,7 +33,7 @@ export async function removeConnection( // Set telemetry for the first node context.telemetry.properties.experience = connectionsToDelete[0].experience.api; - context.telemetry.measurements.removedConnections = connectionsToDelete.length; + context.telemetry.measurements.connectionsToDelete = connectionsToDelete.length; // Confirmation logic - different messages for single vs. multiple deletions const confirmed = await getConfirmationAsInSettings( @@ -91,6 +91,9 @@ export async function removeConnection( // Refresh the tree view ext.connectionsBranchDataProvider.refresh(); + // Set telemetry for successfully deleted connections + context.telemetry.measurements.connectionsDeleted = successCount; + // Show summary message if (connectionsToDelete.length === 1) { // For single connection, show success or error message From 6464580c379d2473f00278c932f397c0218fae86 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 21 Nov 2025 13:36:55 +0100 Subject: [PATCH 06/11] feat: logging errror message / code --- src/commands/removeConnection/removeConnection.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index 8f403c91a..5b3135d10 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -40,11 +40,11 @@ export async function removeConnection( l10n.t('Are you sure?'), connectionsToDelete.length === 1 ? l10n.t('Delete "{connectionName}"?', { connectionName: connectionsToDelete[0].cluster.name }) + - '\n' + - l10n.t('This cannot be undone.') + '\n' + + l10n.t('This cannot be undone.') : l10n.t('Delete {count} connections?', { count: connectionsToDelete.length }) + - '\n' + - l10n.t('This cannot be undone.'), + '\n' + + l10n.t('This cannot be undone.'), 'delete', ); @@ -84,6 +84,10 @@ export async function removeConnection( error: error instanceof Error ? error.message : String(error), }), ); + + context.telemetry.properties.error = 'RemoveConnectionError'; + context.telemetry.properties.errorMessage = error instanceof Error ? error.message : String(error); + failureCount++; } } From c0049350a3068d56017732ade6478b0ea06b4d97 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Fri, 21 Nov 2025 15:16:48 +0100 Subject: [PATCH 07/11] feat: improved connection removal confirmation message --- src/commands/removeConnection/removeConnection.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index 5b3135d10..1b486ca4d 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -36,6 +36,8 @@ export async function removeConnection( context.telemetry.measurements.connectionsToDelete = connectionsToDelete.length; // Confirmation logic - different messages for single vs. multiple deletions + const expectedConfirmationWord = + connectionsToDelete.length === 1 ? 'delete' : connectionsToDelete.length.toString(); const confirmed = await getConfirmationAsInSettings( l10n.t('Are you sure?'), connectionsToDelete.length === 1 @@ -45,7 +47,7 @@ export async function removeConnection( : l10n.t('Delete {count} connections?', { count: connectionsToDelete.length }) + '\n' + l10n.t('This cannot be undone.'), - 'delete', + expectedConfirmationWord, ); if (!confirmed) { From e2a5372c50ea6769916a86a47b42c95d1a599dc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:01:02 +0000 Subject: [PATCH 08/11] Initial plan From 1c3d3283bf8b16adc3339008a9e269f24ae978a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:01:52 +0000 Subject: [PATCH 09/11] Initial plan From 58d5bfbcf9365727021a31f95dae450e8d6807b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:04:39 +0000 Subject: [PATCH 10/11] Add comment and errorCount metric to telemetry Co-authored-by: tnaum-ms <171359267+tnaum-ms@users.noreply.github.com> --- src/commands/removeConnection/removeConnection.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index 1b486ca4d..483e2bad6 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -87,6 +87,8 @@ export async function removeConnection( }), ); + // Note: When multiple deletions fail, we intentionally capture only the last error's + // details in telemetry. The errorCount metric below provides context on total failures. context.telemetry.properties.error = 'RemoveConnectionError'; context.telemetry.properties.errorMessage = error instanceof Error ? error.message : String(error); @@ -99,6 +101,7 @@ export async function removeConnection( // Set telemetry for successfully deleted connections context.telemetry.measurements.connectionsDeleted = successCount; + context.telemetry.measurements.errorCount = failureCount; // Show summary message if (connectionsToDelete.length === 1) { From 962c7624600e95c7cf645617dff3359a620d6106 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:05:57 +0000 Subject: [PATCH 11/11] Add warning message for empty connection selection in removeConnection Co-authored-by: tnaum-ms <171359267+tnaum-ms@users.noreply.github.com> --- l10n/bundle.l10n.json | 1 + src/commands/removeConnection/removeConnection.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index ee8daa6ca..5bf4af0f6 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -514,6 +514,7 @@ "No Azure VMs found with tag \"{tagName}\" in subscription \"{subscriptionName}\".": "No Azure VMs found with tag \"{tagName}\" in subscription \"{subscriptionName}\".", "No collection selected.": "No collection selected.", "No commands found in this document.": "No commands found in this document.", + "No connections selected to remove.": "No connections selected to remove.", "No Connectivity": "No Connectivity", "No credentials found for id {credentialId}": "No credentials found for id {credentialId}", "No credentials found for the selected cluster.": "No credentials found for the selected cluster.", diff --git a/src/commands/removeConnection/removeConnection.ts b/src/commands/removeConnection/removeConnection.ts index 1b486ca4d..022cadf29 100644 --- a/src/commands/removeConnection/removeConnection.ts +++ b/src/commands/removeConnection/removeConnection.ts @@ -28,6 +28,7 @@ export async function removeConnection( } if (connectionsToDelete.length === 0) { + ext.outputChannel.warn(l10n.t('No connections selected to remove.')); return; }