Skip to content
Open
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
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 @@ -243,6 +244,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 @@ -309,9 +311,11 @@
"Clipboard is empty.": "Clipboard is empty.",
"Cluster": "Cluster",
"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 not found (DNS resolution failed)": "Cluster not found (DNS resolution failed)",
"Cluster support unknown $(info)": "Cluster support unknown $(info)",
"Cluster-routed via node port": "Cluster-routed via node port",
"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 @@ -435,6 +439,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",
"Default kubeconfig": "Default kubeconfig",
"Delete": "Delete",
"Delete \"{connectionName}\"?": "Delete \"{connectionName}\"?",
Expand Down Expand Up @@ -622,8 +627,10 @@
"Failed to list services in \"{0}\"": "Failed to list services in \"{0}\"",
"Failed to list services in namespace \"{0}\": {1}. Check your RBAC permissions.": "Failed to list services in namespace \"{0}\": {1}. Check your RBAC permissions.",
"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 for \"{0}\"": "Failed to load databases for \"{0}\"",
"Failed to load databases: {0}": "Failed to load databases: {0}",
"Failed to load kubeconfig from \"{0}\": {1}. Ensure the file exists and contains valid YAML.": "Failed to load kubeconfig from \"{0}\": {1}. Ensure the file exists and contains valid YAML.",
"Failed to load kubeconfig from pasted YAML: {0}. Ensure the clipboard contains a valid kubeconfig document.": "Failed to load kubeconfig from pasted YAML: {0}. Ensure the clipboard contains a valid kubeconfig document.",
"Failed to load kubeconfig: {0}. Ensure a kubeconfig file exists at the Kubernetes default kubeconfig path or the KUBECONFIG environment variable is set.": "Failed to load kubeconfig: {0}. Ensure a kubeconfig file exists at the Kubernetes default kubeconfig path or the KUBECONFIG environment variable is set.",
Expand Down Expand Up @@ -674,6 +681,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 @@ -844,7 +855,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 performance rating": "Loading performance rating",
Expand Down Expand Up @@ -913,12 +926,14 @@
"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 databases match your filter.": "No databases match your filter.",
"No DocumentDB services found in this context.": "No DocumentDB services found in this context.",
"No DocumentDB services found in this namespace.": "No DocumentDB services found in this namespace.",
"No DocumentDB targets were found in context \"{0}\". DKO resources are preferred, and generic fallback currently looks for DocumentDB gateway services.": "No DocumentDB targets were found in context \"{0}\". DKO resources are preferred, and generic fallback currently looks for DocumentDB gateway services.",
Expand Down Expand Up @@ -977,6 +992,7 @@
"Only pasted kubeconfig sources can be viewed. Use \"Edit Kubeconfig\" for file sources.": "Only pasted kubeconfig sources can be viewed. Use \"Edit Kubeconfig\" for file sources.",
"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 @@ -1241,6 +1257,7 @@
"Stopped {0} port-forward tunnel(s) for kubeconfig source \"{1}\".": "Stopped {0} port-forward tunnel(s) for kubeconfig source \"{1}\".",
"Stopping {0}": "Stopping {0}",
"Stopping task...": "Stopping task...",
"Storage size": "Storage size",
"Submit": "Submit",
"Submit Feedback": "Submit Feedback",
"Submitting...": "Submitting...",
Expand Down Expand Up @@ -1368,7 +1385,9 @@
"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. ",
"They look like binary files rather than kubeconfigs.": "They look like binary files rather than kubeconfigs.",
"This cannot be undone.": "This cannot be undone.",
"This cluster has no databases yet.": "This cluster has no databases yet.",
"This ClusterIP service requires port-forwarding. Confirm or change the local port to forward to {0}/{1}:{2}.": "This ClusterIP service requires port-forwarding. Confirm or change the local port to forward to {0}/{1}:{2}.",
"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 @@ -1399,6 +1418,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 @@ -1505,6 +1525,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 DocumentDB cluster. Please sign in (by expanding the node \"{0}\") and try again.": "You are not signed in to the DocumentDB 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 @@ -630,6 +630,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 @@ -908,6 +922,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 @@ -1251,6 +1277,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 { openClusterWebview } 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 = openClusterWebview({
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 @@ -75,6 +75,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 @@ -691,6 +717,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
Loading