From 4a1b7f623c822f26c74a369dffc818ce04e3d84f Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 14 Apr 2026 13:10:47 +0200 Subject: [PATCH 1/9] Feature: Add selected authentication method and connection user to connection models and tooltips --- .../ConnectionsBranchDataProvider.ts | 2 + .../connections-view/DocumentDBClusterItem.ts | 92 ++++++++++++++++--- src/tree/connections-view/FolderItem.ts | 2 + .../LocalEmulators/LocalEmulatorsItem.ts | 2 + .../models/ConnectionClusterModel.ts | 13 +++ 5 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/tree/connections-view/ConnectionsBranchDataProvider.ts b/src/tree/connections-view/ConnectionsBranchDataProvider.ts index 052054c35..359122a9e 100644 --- a/src/tree/connections-view/ConnectionsBranchDataProvider.ts +++ b/src/tree/connections-view/ConnectionsBranchDataProvider.ts @@ -150,6 +150,8 @@ export class ConnectionsBranchDataProvider extends BaseExtendedTreeDataProvider< dbExperience: DocumentDBExperience, connectionString: connection.secrets.connectionString, emulatorConfiguration: connection.properties.emulatorConfiguration, + selectedAuthMethod: connection.properties.selectedAuthMethod, + connectionUser: connection.secrets.nativeAuthConfig?.connectionUser, }; ext.outputChannel.trace( diff --git a/src/tree/connections-view/DocumentDBClusterItem.ts b/src/tree/connections-view/DocumentDBClusterItem.ts index 34bcbbf69..c6ebacdb2 100644 --- a/src/tree/connections-view/DocumentDBClusterItem.ts +++ b/src/tree/connections-view/DocumentDBClusterItem.ts @@ -13,7 +13,13 @@ import * as l10n from '@vscode/l10n'; import * as vscode from 'vscode'; import { nonNullProp } from '../../utils/nonNull'; -import { authMethodFromString, AuthMethodId, authMethodsFromString } from '../../documentdb/auth/AuthMethod'; +import { + authMethodFromString, + AuthMethodId, + authMethodsFromString, + getAuthMethod, + isSupportedAuthMethod, +} from '../../documentdb/auth/AuthMethod'; import { ClustersClient } from '../../documentdb/ClustersClient'; import { CredentialCache } from '../../documentdb/CredentialCache'; import { DocumentDBConnectionString } from '../../documentdb/utils/DocumentDBConnectionString'; @@ -30,6 +36,14 @@ import { type TreeCluster } from '../models/BaseClusterModel'; import { type TreeElementWithStorageId } from '../TreeElementWithStorageId'; import { type ConnectionClusterModel } from './models/ConnectionClusterModel'; +/** + * Escapes markdown special characters so user-provided text is always rendered + * as plain text rather than being interpreted as markdown formatting or links. + */ +function escapeMarkdown(text: string): string { + return text.replace(/[\\`*_{}[\]()#+\-.!|~]/g, '\\$&'); +} + export class DocumentDBClusterItem extends ClusterItemBase implements TreeElementWithStorageId { public override readonly cluster: TreeCluster; @@ -309,19 +323,12 @@ export class DocumentDBClusterItem extends ClusterItemBase 0) { + md.appendMarkdown(`**${l10n.t('Host')}:** ${hosts.join(', ')}\n\n`); + } + + // Auth method + const authMethodId = this.cluster.selectedAuthMethod; + if (authMethodId) { + const authLabel = isSupportedAuthMethod(authMethodId) ? getAuthMethod(authMethodId).label : authMethodId; + md.appendMarkdown(`**${l10n.t('Auth')}:** ${authLabel}\n\n`); + + if (authMethodId === AuthMethodId.NativeAuth && this.cluster.connectionUser) { + md.appendMarkdown(`**${l10n.t('User')}:** ${escapeMarkdown(this.cluster.connectionUser)}\n\n`); + } + } + + // Emulator security notice + if (this.cluster.emulatorConfiguration?.isEmulator) { + if (this.cluster.emulatorConfiguration.disableEmulatorSecurity) { + md.appendMarkdown(`⚠️ **${l10n.t('Security')}:** ${l10n.t('TLS/SSL Disabled')}\n\n`); + } else { + md.appendMarkdown(`✅ **${l10n.t('Security')}:** ${l10n.t('TLS/SSL Enabled')}\n\n`); + } + } + + return md; + } + + /** + * Extracts the host(s) from the connection string for display in the tooltip. + * Returns an empty array if the connection string is unavailable or unparseable. + */ + private getHosts(): string[] { + if (!this.cluster.connectionString) { + return []; + } + try { + return new DocumentDBConnectionString(this.cluster.connectionString).hosts ?? []; + } catch { + return []; + } + } } diff --git a/src/tree/connections-view/FolderItem.ts b/src/tree/connections-view/FolderItem.ts index 398f3a6bb..795fc1022 100644 --- a/src/tree/connections-view/FolderItem.ts +++ b/src/tree/connections-view/FolderItem.ts @@ -107,6 +107,8 @@ export class FolderItem implements TreeElement, TreeElementWithContextValue { dbExperience: DocumentDBExperience, connectionString: child?.secrets?.connectionString ?? undefined, emulatorConfiguration: child.properties.emulatorConfiguration, + selectedAuthMethod: child.properties.selectedAuthMethod, + connectionUser: child.secrets?.nativeAuthConfig?.connectionUser, }; ext.outputChannel.trace( diff --git a/src/tree/connections-view/LocalEmulators/LocalEmulatorsItem.ts b/src/tree/connections-view/LocalEmulators/LocalEmulatorsItem.ts index d4fe00261..20fb14d4b 100644 --- a/src/tree/connections-view/LocalEmulators/LocalEmulatorsItem.ts +++ b/src/tree/connections-view/LocalEmulators/LocalEmulatorsItem.ts @@ -69,6 +69,8 @@ export class LocalEmulatorsItem implements TreeElement, TreeElementWithContextVa dbExperience: DocumentDBExperience, connectionString: connection.secrets.connectionString, emulatorConfiguration: emulatorConfiguration, + selectedAuthMethod: connection.properties.selectedAuthMethod, + connectionUser: connection.secrets.nativeAuthConfig?.connectionUser, }; ext.outputChannel.trace( diff --git a/src/tree/connections-view/models/ConnectionClusterModel.ts b/src/tree/connections-view/models/ConnectionClusterModel.ts index 127f7334e..2b4fc24ea 100644 --- a/src/tree/connections-view/models/ConnectionClusterModel.ts +++ b/src/tree/connections-view/models/ConnectionClusterModel.ts @@ -27,4 +27,17 @@ export interface ConnectionClusterModel extends BaseClusterModel { * Present when this connection represents a local emulator instance. */ emulatorConfiguration?: EmulatorConfiguration; + + /** + * The selected authentication method ID (e.g. 'NativeAuth', 'MicrosoftEntraID'). + * Populated from storage when the tree item is built, used for tooltip display. + */ + selectedAuthMethod?: string; + + /** + * The connection username for native (SCRAM) authentication. + * Populated from storage when the tree item is built, used for tooltip display. + * Never contains a password. + */ + connectionUser?: string; } From d0754ee41f56e5636d59d9a4ef900ed03c5eee58 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 15:04:57 +0200 Subject: [PATCH 2/9] feat: add formatSize utility for formatting byte sizes --- src/utils/formatSize.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/utils/formatSize.ts diff --git a/src/utils/formatSize.ts b/src/utils/formatSize.ts new file mode 100644 index 000000000..2767466df --- /dev/null +++ b/src/utils/formatSize.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Formats a byte size for display. + * Uses binary units (KB, MB, GB, TB) with up to 1 decimal place. + * + * @param bytes The size in bytes to format + * @returns Formatted string representation (e.g., "1.2 MB", "500 B") + */ +export function formatSize(bytes: number): string { + if (bytes < 1024) { + return `${bytes} B`; + } + + const units = ['KB', 'MB', 'GB', 'TB']; + let value = bytes; + let unitIndex = -1; + + while (value >= 1024 && unitIndex < units.length - 1) { + value /= 1024; + unitIndex++; + } + + // Use up to 1 decimal place, drop trailing ".0" + const formatted = value % 1 === 0 ? value.toFixed(0) : value.toFixed(1); + return `${formatted} ${units[unitIndex]}`; +} From 99ffa650c1b294a78bf3858083c7731a9c460a0d Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 15:05:42 +0200 Subject: [PATCH 3/9] feat: add database-level tooltip with size on disk Show the database size on disk in both the tree item description and a markdown tooltip. Size data comes from listDatabases() which already populates DatabaseItemModel.sizeOnDisk at no extra cost. --- src/tree/documentdb/DatabaseItem.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/tree/documentdb/DatabaseItem.ts b/src/tree/documentdb/DatabaseItem.ts index e3b14580e..768877445 100644 --- a/src/tree/documentdb/DatabaseItem.ts +++ b/src/tree/documentdb/DatabaseItem.ts @@ -8,12 +8,21 @@ import * as l10n from '@vscode/l10n'; import * as vscode from 'vscode'; import { ClustersClient, type DatabaseItemModel } from '../../documentdb/ClustersClient'; import { type Experience } from '../../DocumentDBExperiences'; +import { formatSize } from '../../utils/formatSize'; import { type BaseClusterModel, type TreeCluster } from '../models/BaseClusterModel'; import { type TreeElement } from '../TreeElement'; import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { CollectionItem } from './CollectionItem'; +/** + * Escapes markdown special characters so user-provided text is always rendered + * as plain text rather than being interpreted as markdown formatting or links. + */ +function escapeMarkdown(text: string): string { + return text.replace(/[\\`*_{}[\]()#+\-.!|~]/g, '\\$&'); +} + export class DatabaseItem implements TreeElement, TreeElementWithExperience, TreeElementWithContextValue { public readonly id: string; public readonly experience: Experience; @@ -62,12 +71,34 @@ export class DatabaseItem implements TreeElement, TreeElementWithExperience, Tre } getTreeItem(): vscode.TreeItem { + // Show size on disk as the description (e.g., "1.2 MB") + const description = + typeof this.databaseInfo.sizeOnDisk === 'number' ? formatSize(this.databaseInfo.sizeOnDisk) : undefined; + return { id: this.id, contextValue: this.contextValue, label: this.databaseInfo.name, + description, + tooltip: this.buildTooltip(), iconPath: new vscode.ThemeIcon('database'), // TODO: create our own icon here, this one's shape can change collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, }; } + + /** + * Builds a markdown tooltip showing the database name and size on disk. + */ + private buildTooltip(): vscode.MarkdownString { + const md = new vscode.MarkdownString(); + md.isTrusted = false; + + md.appendMarkdown(`### ${escapeMarkdown(this.databaseInfo.name)}\n\n`); + + if (typeof this.databaseInfo.sizeOnDisk === 'number') { + md.appendMarkdown(`**${l10n.t('Size on Disk')}:** ${formatSize(this.databaseInfo.sizeOnDisk)}\n\n`); + } + + return md; + } } From 9f2fdcc3811f2a5e17b8cdb125f6c023f0426aa3 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 15:06:28 +0200 Subject: [PATCH 4/9] feat: add collection-level tooltip with type and document count Show the collection type (collection/view/timeseries) as a badge and document count in a markdown tooltip. Also display the parent database name for context. All data is already available from listCollections() and the async document count loader. --- src/tree/documentdb/CollectionItem.ts | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/tree/documentdb/CollectionItem.ts b/src/tree/documentdb/CollectionItem.ts index 48dc6d35a..415238cc4 100644 --- a/src/tree/documentdb/CollectionItem.ts +++ b/src/tree/documentdb/CollectionItem.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createContextValue } from '@microsoft/vscode-azext-utils'; +import * as l10n from '@vscode/l10n'; import * as vscode from 'vscode'; import { ClustersClient, type CollectionItemModel, type DatabaseItemModel } from '../../documentdb/ClustersClient'; import { type Experience } from '../../DocumentDBExperiences'; @@ -16,6 +17,14 @@ import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { DocumentsItem } from './DocumentsItem'; import { IndexesItem } from './IndexesItem'; +/** + * Escapes markdown special characters so user-provided text is always rendered + * as plain text rather than being interpreted as markdown formatting or links. + */ +function escapeMarkdown(text: string): string { + return text.replace(/[\\`*_{}[\]()#+\-.!|~]/g, '\\$&'); +} + export class CollectionItem implements TreeElement, TreeElementWithExperience, TreeElementWithContextValue { public readonly id: string; public readonly experience: Experience; @@ -98,8 +107,35 @@ export class CollectionItem implements TreeElement, TreeElementWithExperience, T contextValue: this.contextValue, label: this.collectionInfo.name, description, + tooltip: this.buildTooltip(), iconPath: new vscode.ThemeIcon('folder-library'), collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, }; } + + /** + * Builds a markdown tooltip showing the collection name, type, and document count. + */ + private buildTooltip(): vscode.MarkdownString { + const md = new vscode.MarkdownString(); + md.isTrusted = false; + + md.appendMarkdown(`### ${escapeMarkdown(this.collectionInfo.name)}\n\n`); + + // Type badge (collection, view, timeseries) + const collectionType = this.collectionInfo.type ?? 'collection'; + md.appendMarkdown(`\`${collectionType}\`\n\n`); + + md.appendMarkdown('---\n\n'); + + // Database context + md.appendMarkdown(`**${l10n.t('Database')}:** ${escapeMarkdown(this.databaseInfo.name)}\n\n`); + + // Document count + if (typeof this.documentCount === 'number') { + md.appendMarkdown(`**${l10n.t('Documents')}:** ${formatDocumentCount(this.documentCount)}\n\n`); + } + + return md; + } } From 4bc014d49af2cd495608c6b478adc5e861fc7c67 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 15:09:41 +0200 Subject: [PATCH 5/9] chore: update l10n bundle for tooltip strings --- l10n/bundle.l10n.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index a4493f78a..2449a3937 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -118,11 +118,9 @@ "▶️ Run Command": "▶️ Run Command", "► Task '{taskName}' starting...": "► Task '{taskName}' starting...", "○ Task '{taskName}' initializing...": "○ Task '{taskName}' initializing...", - "⚠️ **Security:** TLS/SSL Disabled": "⚠️ **Security:** TLS/SSL Disabled", "⚠️ existing collection": "⚠️ existing collection", "⚠ TLS/SSL Disabled": "⚠ TLS/SSL Disabled", "⚠️ Warning: This will modify the existing collection. Documents with matching _id values will be handled based on your conflict resolution setting.": "⚠️ Warning: This will modify the existing collection. Documents with matching _id values will be handled based on your conflict resolution setting.", - "✅ **Security:** TLS/SSL Enabled": "✅ **Security:** TLS/SSL Enabled", "✓ Task '{taskName}' completed successfully. {message}": "✓ Task '{taskName}' completed successfully. {message}", "$(add) Create...": "$(add) Create...", "$(arrow-left) Go Back": "$(arrow-left) Go Back", @@ -184,6 +182,7 @@ "Are you sure?": "Are you sure?", "Ask Copilot to generate the query for you": "Ask Copilot to generate the query for you", "Attempting to authenticate with \"{cluster}\"…": "Attempting to authenticate with \"{cluster}\"…", + "Auth": "Auth", "Authenticate to connect with your DocumentDB cluster": "Authenticate to connect with your DocumentDB cluster", "Authenticate to Connect with Your DocumentDB Cluster": "Authenticate to Connect with Your DocumentDB Cluster", "Authenticate using a username and password": "Authenticate using a username and password", @@ -326,6 +325,7 @@ "Credentials updated successfully.": "Credentials updated successfully.", "Data shown was correct": "Data shown was correct", "Data shown was incorrect": "Data shown was incorrect", + "Database": "Database", "database \"{0}\"": "database \"{0}\"", "Database name cannot be longer than 64 characters.": "Database name cannot be longer than 64 characters.", "Database name cannot contain any of the following characters: \"{0}{1}\"": "Database name cannot contain any of the following characters: \"{0}{1}\"", @@ -548,6 +548,7 @@ "Hide Index…": "Hide Index…", "Hiding index…": "Hiding index…", "HIGH PRIORITY": "HIGH PRIORITY", + "Host": "Host", "How do you want to connect?": "How do you want to connect?", "How should conflicts be handled during the copy operation?": "How should conflicts be handled during the copy operation?", "How would you rate Query Insights?": "How would you rate Query Insights?", @@ -846,6 +847,7 @@ "Save to the database": "Save to the database", "Saving \"{path}\" will update the entity \"{name}\" to the cloud.": "Saving \"{path}\" will update the entity \"{name}\" to the cloud.", "Saving credentials for \"{clusterName}\"…": "Saving credentials for \"{clusterName}\"…", + "Security": "Security", "See output for more details.": "See output for more details.", "Select {0}": "Select {0}", "Select {mongoExecutableFileName}": "Select {mongoExecutableFileName}", @@ -886,6 +888,7 @@ "Signed in to tenant \"{0}\"": "Signed in to tenant \"{0}\"", "Signing out programmatically is not supported. You must sign out by selecting the account in the Accounts menu and choosing Sign Out.": "Signing out programmatically is not supported. You must sign out by selecting the account in the Accounts menu and choosing Sign Out.", "Simulated failure at step {0} for testing purposes": "Simulated failure at step {0} for testing purposes", + "Size on Disk": "Size on Disk", "Skip": "Skip", "Skip and Log (continue)": "Skip and Log (continue)", "Skip for now": "Skip for now", @@ -1014,6 +1017,8 @@ "This will also delete {0}.": "This will also delete {0}.", "This will prevent the query planner from using this index.": "This will prevent the query planner from using this index.", "Timed out trying to execute the Mongo script. To use a longer timeout, modify the VS Code 'mongo.shell.timeout' setting.": "Timed out trying to execute the Mongo script. To use a longer timeout, modify the VS Code 'mongo.shell.timeout' setting.", + "TLS/SSL Disabled": "TLS/SSL Disabled", + "TLS/SSL Enabled": "TLS/SSL Enabled", "To connect to Azure resources, you need to sign in to Azure accounts.": "To connect to Azure resources, you need to sign in to Azure accounts.", "TODO: Share the steps needed to reliably reproduce the problem. Please include actual and expected results.": "TODO: Share the steps needed to reliably reproduce the problem. Please include actual and expected results.", "Too many arguments. Expecting 0 or 1 argument(s) to {constructorCall}": "Too many arguments. Expecting 0 or 1 argument(s) to {constructorCall}", @@ -1064,6 +1069,7 @@ "URL handling aborted. Connection was unsuccessful or the specified database/collection does not exist.": "URL handling aborted. Connection was unsuccessful or the specified database/collection does not exist.", "Use anyway": "Use anyway", "Use projection to return only necessary fields. This reduces network transfer and memory usage, especially important for documents with large embedded arrays or binary data.": "Use projection to return only necessary fields. This reduces network transfer and memory usage, especially important for documents with large embedded arrays or binary data.", + "User": "User", "Username and Password": "Username and Password", "Username cannot be empty": "Username cannot be empty", "Username contains characters that cannot be safely encoded.": "Username contains characters that cannot be safely encoded.", From b4975f424d3f9454ed26a025d54d681e9e96be02 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 15:28:00 +0200 Subject: [PATCH 6/9] fix: drop sizeOnDisk from database tooltip, capitalize type badges DocumentDB returns sizeOnDisk as 0, so remove it from the database tooltip and description. Add a 'Database' badge to the database tooltip for consistency. Capitalize the collection type badge (Collection, View, Timeseries). --- l10n/bundle.l10n.json | 1 - src/tree/documentdb/CollectionItem.ts | 5 +++-- src/tree/documentdb/DatabaseItem.ts | 12 ++---------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 2449a3937..4fc0f9cc1 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -888,7 +888,6 @@ "Signed in to tenant \"{0}\"": "Signed in to tenant \"{0}\"", "Signing out programmatically is not supported. You must sign out by selecting the account in the Accounts menu and choosing Sign Out.": "Signing out programmatically is not supported. You must sign out by selecting the account in the Accounts menu and choosing Sign Out.", "Simulated failure at step {0} for testing purposes": "Simulated failure at step {0} for testing purposes", - "Size on Disk": "Size on Disk", "Skip": "Skip", "Skip and Log (continue)": "Skip and Log (continue)", "Skip for now": "Skip for now", diff --git a/src/tree/documentdb/CollectionItem.ts b/src/tree/documentdb/CollectionItem.ts index 415238cc4..c5e79b427 100644 --- a/src/tree/documentdb/CollectionItem.ts +++ b/src/tree/documentdb/CollectionItem.ts @@ -122,9 +122,10 @@ export class CollectionItem implements TreeElement, TreeElementWithExperience, T md.appendMarkdown(`### ${escapeMarkdown(this.collectionInfo.name)}\n\n`); - // Type badge (collection, view, timeseries) + // Type badge (Collection, View, Timeseries) const collectionType = this.collectionInfo.type ?? 'collection'; - md.appendMarkdown(`\`${collectionType}\`\n\n`); + const capitalizedType = collectionType.charAt(0).toUpperCase() + collectionType.slice(1); + md.appendMarkdown(`\`${capitalizedType}\`\n\n`); md.appendMarkdown('---\n\n'); diff --git a/src/tree/documentdb/DatabaseItem.ts b/src/tree/documentdb/DatabaseItem.ts index 768877445..c6a8eef47 100644 --- a/src/tree/documentdb/DatabaseItem.ts +++ b/src/tree/documentdb/DatabaseItem.ts @@ -8,7 +8,6 @@ import * as l10n from '@vscode/l10n'; import * as vscode from 'vscode'; import { ClustersClient, type DatabaseItemModel } from '../../documentdb/ClustersClient'; import { type Experience } from '../../DocumentDBExperiences'; -import { formatSize } from '../../utils/formatSize'; import { type BaseClusterModel, type TreeCluster } from '../models/BaseClusterModel'; import { type TreeElement } from '../TreeElement'; import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; @@ -71,15 +70,10 @@ export class DatabaseItem implements TreeElement, TreeElementWithExperience, Tre } getTreeItem(): vscode.TreeItem { - // Show size on disk as the description (e.g., "1.2 MB") - const description = - typeof this.databaseInfo.sizeOnDisk === 'number' ? formatSize(this.databaseInfo.sizeOnDisk) : undefined; - return { id: this.id, contextValue: this.contextValue, label: this.databaseInfo.name, - description, tooltip: this.buildTooltip(), iconPath: new vscode.ThemeIcon('database'), // TODO: create our own icon here, this one's shape can change collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, @@ -87,7 +81,7 @@ export class DatabaseItem implements TreeElement, TreeElementWithExperience, Tre } /** - * Builds a markdown tooltip showing the database name and size on disk. + * Builds a markdown tooltip showing the database name. */ private buildTooltip(): vscode.MarkdownString { const md = new vscode.MarkdownString(); @@ -95,9 +89,7 @@ export class DatabaseItem implements TreeElement, TreeElementWithExperience, Tre md.appendMarkdown(`### ${escapeMarkdown(this.databaseInfo.name)}\n\n`); - if (typeof this.databaseInfo.sizeOnDisk === 'number') { - md.appendMarkdown(`**${l10n.t('Size on Disk')}:** ${formatSize(this.databaseInfo.sizeOnDisk)}\n\n`); - } + md.appendMarkdown(`\`${l10n.t('Database')}\`\n\n`); return md; } From 41a1b57c6dd2ad2877fbae0f33a304239d0d4111 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 15:32:28 +0200 Subject: [PATCH 7/9] chore: remove unused formatSize utility --- src/utils/formatSize.ts | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 src/utils/formatSize.ts diff --git a/src/utils/formatSize.ts b/src/utils/formatSize.ts deleted file mode 100644 index 2767466df..000000000 --- a/src/utils/formatSize.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Formats a byte size for display. - * Uses binary units (KB, MB, GB, TB) with up to 1 decimal place. - * - * @param bytes The size in bytes to format - * @returns Formatted string representation (e.g., "1.2 MB", "500 B") - */ -export function formatSize(bytes: number): string { - if (bytes < 1024) { - return `${bytes} B`; - } - - const units = ['KB', 'MB', 'GB', 'TB']; - let value = bytes; - let unitIndex = -1; - - while (value >= 1024 && unitIndex < units.length - 1) { - value /= 1024; - unitIndex++; - } - - // Use up to 1 decimal place, drop trailing ".0" - const formatted = value % 1 === 0 ? value.toFixed(0) : value.toFixed(1); - return `${formatted} ${units[unitIndex]}`; -} From aeb996096ea35fd76652a64ad144913d8f4988c2 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 16:14:14 +0200 Subject: [PATCH 8/9] fix: escape hosts and auth label in cluster tooltip markdown Escape each host and the auth-method label through escapeMarkdown() before appending them as markdown, preventing user-controlled values from rendering unexpected formatting or links. --- src/tree/connections-view/DocumentDBClusterItem.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tree/connections-view/DocumentDBClusterItem.ts b/src/tree/connections-view/DocumentDBClusterItem.ts index c6ebacdb2..e08264dfb 100644 --- a/src/tree/connections-view/DocumentDBClusterItem.ts +++ b/src/tree/connections-view/DocumentDBClusterItem.ts @@ -359,14 +359,15 @@ export class DocumentDBClusterItem extends ClusterItemBase 0) { - md.appendMarkdown(`**${l10n.t('Host')}:** ${hosts.join(', ')}\n\n`); + const escapedHosts = hosts.map((host) => escapeMarkdown(host)); + md.appendMarkdown(`**${l10n.t('Host')}:** ${escapedHosts.join(', ')}\n\n`); } // Auth method const authMethodId = this.cluster.selectedAuthMethod; if (authMethodId) { const authLabel = isSupportedAuthMethod(authMethodId) ? getAuthMethod(authMethodId).label : authMethodId; - md.appendMarkdown(`**${l10n.t('Auth')}:** ${authLabel}\n\n`); + md.appendMarkdown(`**${l10n.t('Auth')}:** ${escapeMarkdown(authLabel)}\n\n`); if (authMethodId === AuthMethodId.NativeAuth && this.cluster.connectionUser) { md.appendMarkdown(`**${l10n.t('User')}:** ${escapeMarkdown(this.cluster.connectionUser)}\n\n`); From 9ee1629eb8c9b4c2aee7462a1d69dd771c843d51 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Thu, 16 Apr 2026 16:19:48 +0200 Subject: [PATCH 9/9] fix: resolve unsafe enum comparison lint error in tooltip Guard the AuthMethodId.NativeAuth comparison with isSupportedAuthMethod() to satisfy the @typescript-eslint/no-unsafe-enum-comparison rule. --- src/tree/connections-view/DocumentDBClusterItem.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tree/connections-view/DocumentDBClusterItem.ts b/src/tree/connections-view/DocumentDBClusterItem.ts index e08264dfb..9bd7d2f06 100644 --- a/src/tree/connections-view/DocumentDBClusterItem.ts +++ b/src/tree/connections-view/DocumentDBClusterItem.ts @@ -366,10 +366,11 @@ export class DocumentDBClusterItem extends ClusterItemBase