Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 22 additions & 0 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
" and ": " and ",
" on GitHub.": " on GitHub.",
" or ": " or ",
"—": "—",
", No public IP or FQDN found.": ", No public IP or FQDN found.",
"! Task '{taskName}' failed. {message}": "! Task '{taskName}' failed. {message}",
".limit({0}) exceeds the display batch size ({1}), so only {1} documents will be shown. Use .toArray() to retrieve all {0}, or increase \"{2}\" in Settings.": ".limit({0}) exceeds the display batch size ({1}), so only {1} documents will be shown. Use .toArray() to retrieve all {0}, or increase \"{2}\" in Settings.",
Expand Down Expand Up @@ -219,6 +220,7 @@
"Authentication is required to run this action.": "Authentication is required to run this action.",
"Authentication is required to use this migration provider.": "Authentication is required to use this migration provider.",
"Authentication: {0} | Database: {1}": "Authentication: {0} | Database: {1}",
"Avg. document size": "Avg. document size",
"Azure account added successfully.": "Azure account added successfully.",
"Azure account management failed: {0}": "Azure account management failed: {0}",
"Azure account management was cancelled by user.": "Azure account management was cancelled by user.",
Expand Down Expand Up @@ -275,7 +277,9 @@
"Clipboard is empty.": "Clipboard is empty.",
"Close tips": "Close tips",
"Cluster metadata not initialized. Client may not be properly connected.": "Cluster metadata not initialized. Client may not be properly connected.",
"Cluster navigation": "Cluster navigation",
"Cluster support unknown $(info)": "Cluster support unknown $(info)",
"Collection": "Collection",
"collection \"{0}\"": "collection \"{0}\"",
"Collection \"{0}\" from database \"{1}\" has been marked for copy. You can now paste this collection into any database or existing collection using the \"Paste Collection...\" option in the context menu.": "Collection \"{0}\" from database \"{1}\" has been marked for copy. You can now paste this collection into any database or existing collection using the \"Paste Collection...\" option in the context menu.",
"Collection name cannot begin with the system. prefix (Reserved for internal use).": "Collection name cannot begin with the system. prefix (Reserved for internal use).",
Expand Down Expand Up @@ -379,6 +383,7 @@
"Database name is required when collection is specified": "Database name is required when collection is specified",
"Database name is required.": "Database name is required.",
"Database: \"{databaseName}\"": "Database: \"{databaseName}\"",
"Databases": "Databases",
"Delete": "Delete",
"Delete \"{connectionName}\"?": "Delete \"{connectionName}\"?",
"Delete \"{nodeName}\"?": "Delete \"{nodeName}\"?",
Expand Down Expand Up @@ -545,7 +550,9 @@
"Failed to initialize Azure management clients": "Failed to initialize Azure management clients",
"Failed to initialize task": "Failed to initialize task",
"Failed to load {0}": "Failed to load {0}",
"Failed to load collections: {0}": "Failed to load collections: {0}",
"Failed to load custom prompt template from {path}: {error}. Using built-in template.": "Failed to load custom prompt template from {path}: {error}. Using built-in template.",
"Failed to load databases: {0}": "Failed to load databases: {0}",
"Failed to load selected items from storage.": "Failed to load selected items from storage.",
"Failed to load template file for {type}: {error}": "Failed to load template file for {type}: {error}",
"Failed to modify index: {error}": "Failed to modify index: {error}",
Expand Down Expand Up @@ -586,6 +593,10 @@
"Fair": "Fair",
"Fast execution": "Fast execution",
"Fetch Overhead": "Fetch Overhead",
"Filter collections": "Filter collections",
"Filter collections by name": "Filter collections by name",
"Filter databases": "Filter databases",
"Filter databases by name": "Filter databases by name",
"Filter: Enter the DocumentDB query filter": "Filter: Enter the DocumentDB query filter",
"Find Query": "Find Query",
"Finished importing": "Finished importing",
Expand Down Expand Up @@ -734,7 +745,9 @@
"Loading Azure Accounts Used for Service Discovery…": "Loading Azure Accounts Used for Service Discovery…",
"Loading cluster details for \"{cluster}\"": "Loading cluster details for \"{cluster}\"",
"Loading Clusters…": "Loading Clusters…",
"Loading collections…": "Loading collections…",
"Loading Content": "Loading Content",
"Loading databases…": "Loading databases…",
"Loading document {num} of {countUri}": "Loading document {num} of {countUri}",
"Loading documents…": "Loading documents…",
"Loading resources...": "Loading resources...",
Expand Down Expand Up @@ -796,13 +809,15 @@
"No code to run. Place the cursor in a code block.": "No code to run. Place the cursor in a code block.",
"No collection has been marked for copy. Please use \"Copy Collection...\" first to select a source collection.": "No collection has been marked for copy. Please use \"Copy Collection...\" first to select a source collection.",
"No collection selected.": "No collection selected.",
"No collections match your filter.": "No collections match your filter.",
"No connections selected to remove.": "No connections selected to remove.",
"No Connectivity": "No Connectivity",
"No credentials found for cluster \"{0}\"": "No credentials found for cluster \"{0}\"",
"No credentials found for cluster {0}": "No credentials found for cluster {0}",
"No credentials found for id {clusterId}": "No credentials found for id {clusterId}",
"No credentials found for the selected cluster.": "No credentials found for the selected cluster.",
"No database connected": "No database connected",
"No databases match your filter.": "No databases match your filter.",
"No folder selected.": "No folder selected.",
"No index changes needed at this time.": "No index changes needed at this time.",
"No index selected.": "No index selected.",
Expand Down Expand Up @@ -840,6 +855,7 @@
"OK": "OK",
"Open \"{0}.{1}\" in Query Playground": "Open \"{0}.{1}\" in Query Playground",
"Open batch size setting": "Open batch size setting",
"Open Cluster Overview": "Open Cluster Overview",
"Open Collection": "Open Collection",
"Open collection \"{0}.{1}\" in Collection View": "Open collection \"{0}.{1}\" in Collection View",
"Open current query in a Query Playground": "Open current query in a Query Playground",
Expand Down Expand Up @@ -1057,6 +1073,7 @@
"Starts with mongodb:// or mongodb+srv://": "Starts with mongodb:// or mongodb+srv://",
"Stopping {0}": "Stopping {0}",
"Stopping task...": "Stopping task...",
"Storage size": "Storage size",
"Submit": "Submit",
"Submit Feedback": "Submit Feedback",
"Submitting...": "Submitting...",
Expand Down Expand Up @@ -1163,6 +1180,8 @@
"The worker for this cluster is busy executing a playground": "The worker for this cluster is busy executing a playground",
"These signals help us improve, but more context in a discussion, issue report, or a direct message adds even more value. ": "These signals help us improve, but more context in a discussion, issue report, or a direct message adds even more value. ",
"This cannot be undone.": "This cannot be undone.",
"This cluster has no databases yet.": "This cluster has no databases yet.",
"This database has no collections yet.": "This database has no collections yet.",
"This field is not set": "This field is not set",
"This functionality requires installing the Azure Account extension.": "This functionality requires installing the Azure Account extension.",
"This functionality requires updating the Azure Account extension to at least version \"{0}\".": "This functionality requires updating the Azure Account extension to at least version \"{0}\".",
Expand Down Expand Up @@ -1191,6 +1210,7 @@
"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.",
"Total documents to import: {0}": "Total documents to import: {0}",
"Total index size": "Total index size",
"Total time taken to execute the query on the server": "Total time taken to execute the query on the server",
"Transforming Stage 2 response to UI format": "Transforming Stage 2 response to UI format",
"Tree View": "Tree View",
Expand Down Expand Up @@ -1284,6 +1304,8 @@
"Yes, save my credentials": "Yes, save my credentials",
"You are already signed in to tenant \"{0}\"": "You are already signed in to tenant \"{0}\"",
"You are not signed in to an Azure account. Please sign in.": "You are not signed in to an Azure account. Please sign in.",
"You are not signed in to the cluster \"{0}\". Expand it in the tree to sign in, then try again.": "You are not signed in to the cluster \"{0}\". Expand it in the tree to sign in, then try again.",
"You are not signed in to the cluster. Expand it in the tree to sign in, then try again.": "You are not signed in to the cluster. Expand it in the tree to sign in, then try again.",
"You are not signed in to the MongoDB Cluster. Please sign in (by expanding the node \"{0}\") and try again.": "You are not signed in to the MongoDB Cluster. Please sign in (by expanding the node \"{0}\") and try again.",
"You can disable this confirmation by setting \"{0}\" to false.": "You can disable this confirmation by setting \"{0}\" to false.",
"You can increase the timeout in Settings:": "You can increase the timeout in Settings:",
Expand Down
30 changes: 30 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,20 @@
"title": "Open Collection",
"icon": "$(files)"
},
{
"//": "Open Cluster Dashboard / Home Page",
"category": "DocumentDB",
"command": "vscode-documentdb.command.clusterView.open",
"title": "Open Cluster Overview",
"icon": "$(dashboard)"
},
{
"//": "Open Cluster Dashboard / Home Page (inline button)",
"category": "DocumentDB",
"command": "vscode-documentdb.command.clusterView.open.inline",
"title": "Open Cluster Overview",
"icon": "$(dashboard)"
},
{
"//": "Copy Collection",
"category": "DocumentDB",
Expand Down Expand Up @@ -794,6 +808,18 @@
"when": "view =~ /azureResourceGroups|azureFocusView/ && viewItem =~ /\\btreeitem_documentdbcluster\\b/i",
"group": "inline"
},
{
"//": "[Cluster] Open Cluster Overview (inline)",
"command": "vscode-documentdb.command.clusterView.open.inline",
"when": "view =~ /connectionsView|discoveryView|azure(ResourceGroups|FocusView)/ && viewItem =~ /\\btreeitem_documentdbcluster\\b/i",
"group": "inline@0"
},
{
"//": "[Cluster] Open Cluster Overview",
"command": "vscode-documentdb.command.clusterView.open",
"when": "view =~ /connectionsView|discoveryView|azure(ResourceGroups|FocusView)/ && viewItem =~ /\\btreeitem_documentdbcluster\\b/i",
"group": "1@0"
},
{
"//": "Create database",
"command": "vscode-documentdb.command.createDatabase",
Expand Down Expand Up @@ -1105,6 +1131,10 @@
"command": "vscode-documentdb.command.containerView.open.inline",
"when": "never"
},
{
"command": "vscode-documentdb.command.clusterView.open.inline",
"when": "never"
},
{
"command": "vscode-documentdb.command.playground.new.inline",
"when": "never"
Expand Down
62 changes: 62 additions & 0 deletions src/commands/openClusterView/openClusterView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type IActionContext } from '@microsoft/vscode-azext-utils';
import * as l10n from '@vscode/l10n';

import { inferViewIdFromTreeId } from '../../documentdb/Views';
import { type ClusterItemBase } from '../../tree/documentdb/ClusterItemBase';
import { trackJourneyCorrelationId } from '../../utils/commandTelemetry';
import { ClusterViewController } from '../../webviews/documentdb/clusterView/clusterViewController';

/**
* Opens the cluster dashboard / home page from a cluster tree node (context
* menu or inline action). Resolves the coordinates the webview needs and
* delegates to {@link openClusterViewInternal}.
*/
export async function openClusterView(context: IActionContext, node: ClusterItemBase): Promise<void> {
// Added manually here as this function can be called bypassing our general command registration.
trackJourneyCorrelationId(context, node);

if (!node) {
throw new Error(l10n.t('No node selected.'));
}

context.telemetry.properties.experience = node.experience?.api;

// Extract viewId from the cluster model, or infer it from the treeId prefix.
const viewId = node.cluster.viewId ?? inferViewIdFromTreeId(node.cluster.treeId);

return openClusterViewInternal(context, {
clusterId: node.cluster.clusterId,
clusterDisplayName: node.cluster.name,
viewId: viewId,
clusterTreeId: node.cluster.treeId,
});
}

/**
* Opens the cluster dashboard from explicit coordinates. Used both by the
* tree-node command above and by the double-click handler (which passes the
* props object directly).
*/
export async function openClusterViewInternal(
_context: IActionContext,
props: {
clusterId: string;
clusterDisplayName: string;
viewId: string;
clusterTreeId: string;
},
): Promise<void> {
const view = new ClusterViewController({
clusterId: props.clusterId,
clusterDisplayName: props.clusterDisplayName,
viewId: props.viewId,
clusterTreeId: props.clusterTreeId,
});

view.revealToForeground();
}
54 changes: 54 additions & 0 deletions src/documentdb/ClustersClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,32 @@ export interface CollectionItemModel {
shardKey?: Record<string, number | string>;
}

/**
* Database-wide statistics, as returned by the `dbStats` command. Used by the
* cluster dashboard to show per-database collection count, index count, and
* total size. All numeric fields default to 0 when the server omits them.
*
* @see https://www.mongodb.com/docs/manual/reference/command/dbStats/
*/
export interface DatabaseStats {
/** Number of collections in the database. */
collections: number;
/** Total number of indexes across all collections in the database. */
indexes: number;
/** Total size in bytes of the uncompressed data held in the database. */
dataSize: number;
/** Total size in bytes of all indexes in the database. */
indexSize: number;
/** Total size in bytes of all documents plus the storage padding. */
storageSize: number;
/**
* Total size in bytes of the database (data + indexes). Mirrors the
* `totalSize` field of `dbStats`; falls back to `dataSize + indexSize`
* when the server does not report it.
*/
totalSize: number;
}

/**
* Find query parameters for MongoDB find operations.
* Each field accepts a JSON string representation of the MongoDB query syntax.
Expand Down Expand Up @@ -666,6 +692,34 @@ export class ClustersClient {
return this._databasesCache ?? undefined;
}

/**
* Returns database-wide statistics via the `dbStats` command. Used by the
* cluster dashboard for the per-database collection count, index count, and
* total size columns.
*
* The caller is responsible for handling failures (e.g. tiers that restrict
* `dbStats`); this method does not swallow errors so the dashboard can show a
* per-row "unavailable" state without masking real connection problems.
*
* @param databaseName - Name of the database to inspect.
* @returns The mapped {@link DatabaseStats}.
*/
async getDatabaseStats(databaseName: string): Promise<DatabaseStats> {
const stats = await this._mongoClient.db(databaseName).command({ dbStats: 1 });

const dataSize = (stats.dataSize as number) ?? 0;
const indexSize = (stats.indexSize as number) ?? 0;

return {
collections: (stats.collections as number) ?? 0,
indexes: (stats.indexes as number) ?? 0,
dataSize,
indexSize,
storageSize: (stats.storageSize as number) ?? 0,
totalSize: (stats.totalSize as number) ?? dataSize + indexSize,
};
}

async listIndexes(databaseName: string, collectionName: string): Promise<IndexItemModel[]> {
const collection = this._mongoClient.db(databaseName).collection(collectionName);
const indexes = await collection.indexes();
Expand Down
29 changes: 29 additions & 0 deletions src/documentdb/ClustersExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { unhideIndex } from '../commands/index.unhideIndex/unhideIndex';
import { learnMoreAboutServiceProvider } from '../commands/learnMoreAboutServiceProvider/learnMoreAboutServiceProvider';
import { newConnection } from '../commands/newConnection/newConnection';
import { newLocalConnection } from '../commands/newLocalConnection/newLocalConnection';
import { openClusterView, openClusterViewInternal } from '../commands/openClusterView/openClusterView';
import { openCollectionView, openCollectionViewInternal } from '../commands/openCollectionView/openCollectionView';
import { openDocumentView } from '../commands/openDocument/openDocument';
import {
Expand Down Expand Up @@ -667,6 +668,34 @@ export class ClustersExtension implements vscode.Disposable {
}),
);

/**
* Cluster dashboard / home page. Like the collection view, it is
* reachable in three ways that share one underlying handler:
* - a double-click on the cluster node (openFromTree),
* - a context-menu "Cluster Overview" action,
* - an inline action button on the cluster node.
*/
registerCommand(
'vscode-documentdb.command.internal.clusterView.open',
withCommandCorrelation(openClusterViewInternal),
);
registerDoubleClickCommand(
'vscode-documentdb.command.internal.clusterView.openFromTree',
withCommandCorrelation(openClusterViewInternal),
doubleClickDebounceDelay,
);
registerCommandWithTreeNodeUnwrapping(
'vscode-documentdb.command.clusterView.open',
withTreeNodeCommandCorrelation(openClusterView),
);
registerCommandWithTreeNodeUnwrapping(
'vscode-documentdb.command.clusterView.open.inline',
withTreeNodeCommandCorrelation((context, node) => {
context.telemetry.properties.activationSource = 'treeNodeInline';
return openClusterView(context, node as ClusterItemBase);
}),
);

registerCommand(
'vscode-documentdb.command.internal.documentView.open',
withCommandCorrelation(openDocumentView),
Expand Down
Loading