Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -329,6 +328,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}\"",
Expand Down Expand Up @@ -551,6 +551,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?",
Expand Down Expand Up @@ -853,6 +854,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}",
Expand Down Expand Up @@ -1023,6 +1025,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}",
Expand Down Expand Up @@ -1073,6 +1077,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.",
Expand Down
2 changes: 2 additions & 0 deletions src/tree/connections-view/ConnectionsBranchDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
94 changes: 80 additions & 14 deletions src/tree/connections-view/DocumentDBClusterItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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, '\\$&');
Comment thread
tnaum-ms marked this conversation as resolved.
}

export class DocumentDBClusterItem extends ClusterItemBase<ConnectionClusterModel> implements TreeElementWithStorageId {
public override readonly cluster: TreeCluster<ConnectionClusterModel>;

Expand Down Expand Up @@ -309,19 +323,12 @@ export class DocumentDBClusterItem extends ClusterItemBase<ConnectionClusterMode
*/
getTreeItem(): vscode.TreeItem {
let description: string | undefined = undefined;
let tooltipMessage: string | undefined = undefined;

if (this.cluster.emulatorConfiguration?.isEmulator) {
// For emulator clusters, show TLS/SSL status if security is disabled
if (this.cluster.emulatorConfiguration?.disableEmulatorSecurity) {
description = l10n.t('⚠ TLS/SSL Disabled');
tooltipMessage = l10n.t('⚠️ **Security:** TLS/SSL Disabled');
} else {
tooltipMessage = l10n.t('✅ **Security:** TLS/SSL Enabled');
}
if (
this.cluster.emulatorConfiguration?.isEmulator &&
this.cluster.emulatorConfiguration?.disableEmulatorSecurity
) {
description = l10n.t('⚠ TLS/SSL Disabled');
}
// Note: ConnectionClusterModel doesn't include Azure-specific fields like SKU.
// For user-added connections, we only show basic cluster name without Azure metadata.

return {
id: this.id,
Expand All @@ -332,7 +339,66 @@ export class DocumentDBClusterItem extends ClusterItemBase<ConnectionClusterMode
? new vscode.ThemeIcon('plug')
: new vscode.ThemeIcon('server-environment'),
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
tooltip: new vscode.MarkdownString(tooltipMessage),
tooltip: this.buildTooltip(),
};
}

/**
* Builds a markdown tooltip showing the connection name, host, auth method,
* username (SCRAM only), and emulator security status.
*
* The cluster name is escaped so it always renders as plain text regardless
* of characters that might otherwise be interpreted as markdown links or formatting.
*/
private buildTooltip(): vscode.MarkdownString {
const md = new vscode.MarkdownString();
md.isTrusted = false;

md.appendMarkdown(`### ${escapeMarkdown(this.cluster.name)}\n\n`);

// Host(s) from the connection string
const hosts = this.getHosts();
if (hosts.length > 0) {
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 isSupported = isSupportedAuthMethod(authMethodId);
const authLabel = isSupported ? getAuthMethod(authMethodId).label : authMethodId;
md.appendMarkdown(`**${l10n.t('Auth')}:** ${escapeMarkdown(authLabel)}\n\n`);

if (isSupported && 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`);
}
Comment thread
tnaum-ms marked this conversation as resolved.
}

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 [];
}
}
}
2 changes: 2 additions & 0 deletions src/tree/connections-view/FolderItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
13 changes: 13 additions & 0 deletions src/tree/connections-view/models/ConnectionClusterModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
37 changes: 37 additions & 0 deletions src/tree/documentdb/CollectionItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -98,8 +107,36 @@ 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';
const capitalizedType = collectionType.charAt(0).toUpperCase() + collectionType.slice(1);
md.appendMarkdown(`\`${capitalizedType}\`\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;
}
}
23 changes: 23 additions & 0 deletions src/tree/documentdb/DatabaseItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ 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;
Expand Down Expand Up @@ -66,8 +74,23 @@ export class DatabaseItem implements TreeElement, TreeElementWithExperience, Tre
id: this.id,
contextValue: this.contextValue,
label: this.databaseInfo.name,
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.
*/
private buildTooltip(): vscode.MarkdownString {
const md = new vscode.MarkdownString();
md.isTrusted = false;

md.appendMarkdown(`### ${escapeMarkdown(this.databaseInfo.name)}\n\n`);

md.appendMarkdown(`\`${l10n.t('Database')}\`\n\n`);

return md;
}
}
Loading