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
1 change: 1 addition & 0 deletions extension.bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ export { randomUtils } from './src/utils/randomUtils';
export { rejectOnTimeout, valueOnTimeout } from './src/utils/timeout';
export { IDisposable, getDocumentTreeItemLabel } from './src/utils/vscodeUtils';
export { wrapError } from './src/utils/wrapError';
export { globalUriHandler } from './src/vscodeUriHandler';

// NOTE: The auto-fix action "source.organizeImports" does weird things with this file, but there doesn't seem to be a way to disable it on a per-file basis so we'll just let it happen
6 changes: 5 additions & 1 deletion l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@
"Select resource group \"{0}\"": "Select resource group \"{0}\"",
"Select sort direction": "Select sort direction",
"Select subscription": "Select subscription",
"Select subscriptions": "Select subscriptions",
"Select Subscriptions...": "Select Subscriptions...",
"Select the Azure Cosmos DB Emulator Type…": "Select the Azure Cosmos DB Emulator Type…",
"Select the error you would like to report": "Select the error you would like to report",
Expand All @@ -601,6 +602,7 @@
"Setting up credentials for server \"{0}\"…": "Setting up credentials for server \"{0}\"…",
"Show history of previous queries": "Show history of previous queries",
"Showing Results": "Showing Results",
"Sign in": "Sign in",
"Sign in to Azure...": "Sign in to Azure...",
"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.",
"Skip for now": "Skip for now",
Expand Down Expand Up @@ -650,6 +652,7 @@
"The \"MongoDB Connections\" functionality has moved to the \"DocumentDB for VS Code\" extension.\n\nIf you had connections saved here in the past, they have been migrated to the new \"Connections View\".\n\nClick to install the new \"DocumentDB for VS Code\" extension.": "The \"MongoDB Connections\" functionality has moved to the \"DocumentDB for VS Code\" extension.\n\nIf you had connections saved here in the past, they have been migrated to the new \"Connections View\".\n\nClick to install the new \"DocumentDB for VS Code\" extension.",
"The Azure Cosmos DB emulator for MongoDB is only supported on Windows, Linux and MacOS (Intel).": "The Azure Cosmos DB emulator for MongoDB is only supported on Windows, Linux and MacOS (Intel).",
"The Azure Cosmos DB emulator is only supported on Windows, Linux and MacOS (Intel).": "The Azure Cosmos DB emulator is only supported on Windows, Linux and MacOS (Intel).",
"The Azure subscription that contains this Cosmos DB resource is not accessible with your current Azure sign-in or selected subscriptions. Sign in with the correct account or update your subscription selection, then try again.": "The Azure subscription that contains this Cosmos DB resource is not accessible with your current Azure sign-in or selected subscriptions. Sign in with the correct account or update your subscription selection, then try again.",
"The collection \"{0}\" already exists in the database \"{1}\".": "The collection \"{0}\" already exists in the database \"{1}\".",
"The collection \"{containerId}\" has been deleted.": "The collection \"{containerId}\" has been deleted.",
"The connection \"{connectionName}\" already exists. Do you want to update it?": "The connection \"{connectionName}\" already exists. Do you want to update it?",
Expand Down Expand Up @@ -696,6 +699,7 @@
"third partition key e.g., /address/zipCode": "third partition key e.g., /address/zipCode",
"This attached account does not have permissions to create a database.": "This attached account does not have permissions to create a database.",
"This cannot be undone.": "This cannot be undone.",
"This Cosmos DB resource isn't available with your current Azure sign-in. Sign in with the correct account or select the correct subscriptions and try again.": "This Cosmos DB resource isn't available with your current Azure sign-in. Sign in with the correct account or select the correct subscriptions and try again.",
"This extension does not support Azure Cosmos DB for": "This extension does not support Azure Cosmos DB for",
"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.",
Expand Down Expand Up @@ -741,7 +745,7 @@
"Unable to execute query due to missing node data. Please connect to a Cosmos DB container node.": "Unable to execute query due to missing node data. Please connect to a Cosmos DB container node.",
"Unable to extract account name from connection string": "Unable to extract account name from connection string",
"Unable to find database \"{0}\" and collection \"{1}\" in resource \"{2}\". Please ensure the resource exists and try again.": "Unable to find database \"{0}\" and collection \"{1}\" in resource \"{2}\". Please ensure the resource exists and try again.",
"Unable to find resource \"{0}\". Please ensure the resource exists and try again.": "Unable to find resource \"{0}\". Please ensure the resource exists and try again.",
"Unable to find resource \"{0}\". Please ensure the following:\n- You are signed in with the correct Azure account.\n- The subscription is selected.\n- The resource exists.": "Unable to find resource \"{0}\". Please ensure the following:\n- You are signed in with the correct Azure account.\n- The subscription is selected.\n- The resource exists.",
"Unable to get query plan due to missing node data. Please connect to a Cosmos DB container node.": "Unable to get query plan due to missing node data. Please connect to a Cosmos DB container node.",
"Unable to parse execution result": "Unable to parse execution result",
"Unable to parse syntax near line {line}, col {column}: {message}": "Unable to parse syntax near line {line}, col {column}: {message}",
Expand Down
151 changes: 131 additions & 20 deletions src/vscodeUriHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { parseAzureResourceId, type ParsedAzureResourceId } from '@microsoft/vsc
import {
callWithTelemetryAndErrorHandling,
nonNullValue,
parseError,
UserCancelledError,
type IActionContext,
} from '@microsoft/vscode-azext-utils';
Expand Down Expand Up @@ -73,6 +74,9 @@ export async function globalUriHandler(uri: vscode.Uri): Promise<void> {
},
);
} catch (error) {
if (error instanceof UserCancelledError) {
throw error;
}
const errMsg = error instanceof Error ? error.message : String(error);
throw new Error(l10n.t('Failed to process URI: {0}', errMsg));
}
Expand Down Expand Up @@ -290,39 +294,51 @@ async function revealAzureResourceInExplorer(
container?: string,
): Promise<TreeElement> {
await vscode.commands.executeCommand('azureResourceGroups.focus');
await ext.rgApiV2.resources.revealAzureResource(resourceId.rawId, {
select: true,
focus: true,
expand: true,
});
await revealAzureResourceWithAccountPrompt(context, resourceId.rawId);

let fulId = resourceId.rawId;
if (database && container) {
fulId = `${resourceId.rawId}${database ? `/${database}${container ? `/${container}` : ''}` : ''}`;
await ext.rgApiV2.resources.revealAzureResource(fulId, {
select: true,
focus: true,
expand: true,
});
await revealAzureResourceWithAccountPrompt(context, fulId);
}

const tryFindRevealedResource = async (): Promise<TreeElement | undefined> => {
const revealedId = await ext.rgApiV2.resources.getSelectedAzureNode();
if (revealedId) {
const strippedId = removeAzureTenantPrefix(revealedId);
return await branchDataProvider.findNodeById(strippedId);
}

return await branchDataProvider.findNodeById(fulId);
};
Comment on lines +305 to +313
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tryFindRevealedResource closes over branchDataProvider, which is declared later in the function. It’s currently safe due to the call order, but it’s easy to break via refactor and can be confusing with block-scoped TDZ rules. Consider moving the branchDataProvider declaration above tryFindRevealedResource to keep variable initialization close to its use.

Copilot uses AI. Check for mistakes.

let resource: TreeElement | undefined;
const branchDataProvider =
resourceId.provider === 'Microsoft.DocumentDB/mongoClusters'
? ext.mongoVCoreBranchDataProvider
: ext.cosmosDBBranchDataProvider;
const revealedId = await ext.rgApiV2.resources.getSelectedAzureNode();
if (revealedId) {
const strippedId = removeAzureTenantPrefix(revealedId);
resource = await branchDataProvider.findNodeById(strippedId);
} else {
resource = await branchDataProvider.findNodeById(fulId);
}
resource = await tryFindRevealedResource();

if (!resource) {
throw new Error(
l10n.t('Unable to find resource "{0}". Please ensure the resource exists and try again.', resourceId.rawId),
);
// If reveal succeeded but we can't locate the node, it's commonly because the user is signed into
// a different Azure account or hasn't selected the correct subscriptions.
await promptToFixAzureAccountAccess(context);

// Retry after the user has potentially changed their Azure sign-in/subscription selection.
await revealAzureResourceWithAccountPrompt(context, resourceId.rawId);
if (database && container) {
await revealAzureResourceWithAccountPrompt(context, fulId);
}

resource = await tryFindRevealedResource();
if (!resource) {
throw new Error(
l10n.t(
'Unable to find resource "{0}". Please ensure the following:\n- You are signed in with the correct Azure account.\n- The subscription is selected.\n- The resource exists.',
resourceId.rawId,
),
);
}
}

context.telemetry.properties.experience = isTreeElementWithExperience(resource)
Expand All @@ -343,6 +359,101 @@ async function revealAzureResourceInExplorer(
return resource;
}

async function promptToFixAzureAccountAccess(context: IActionContext): Promise<void> {
const signIn: vscode.MessageItem = { title: l10n.t('Sign in') };
const selectSubscriptions: vscode.MessageItem = { title: l10n.t('Select subscriptions') };

const choice = await context.ui.showWarningMessage(
l10n.t(
"This Cosmos DB resource isn't available with your current Azure sign-in. Sign in with the correct account or select the correct subscriptions and try again.",
),
{ modal: true, stepName: 'azureAccountMismatch' },
signIn,
selectSubscriptions,
);

if (!choice) {
throw new UserCancelledError('azureAccountMismatch');
}

if (choice.title === signIn.title) {
await vscode.commands.executeCommand('azure-account.login');
} else if (choice.title === selectSubscriptions.title) {
await vscode.commands.executeCommand('azure-account.selectSubscriptions');
}
}

async function revealAzureResourceWithAccountPrompt(context: IActionContext, azureResourceId: string): Promise<void> {
try {
await ext.rgApiV2.resources.revealAzureResource(azureResourceId, {
select: true,
focus: true,
expand: true,
});
} catch (error) {
const parsed = parseError(error);
if (!isSubscriptionNotFoundError(parsed.message)) {
throw error;
}

const signIn: vscode.MessageItem = { title: l10n.t('Sign in') };
const selectSubscriptions: vscode.MessageItem = { title: l10n.t('Select subscriptions') };

const choice = await context.ui.showWarningMessage(
l10n.t(
'The Azure subscription that contains this Cosmos DB resource is not accessible with your current Azure sign-in or selected subscriptions. Sign in with the correct account or update your subscription selection, then try again.',
),
{ modal: true, stepName: 'subscriptionNotFound' },
signIn,
selectSubscriptions,
);

if (!choice) {
throw new UserCancelledError('subscriptionNotFound');
}

if (choice.title === signIn.title) {
await vscode.commands.executeCommand('azure-account.login');
} else if (choice.title === selectSubscriptions.title) {
await vscode.commands.executeCommand('azure-account.selectSubscriptions');
}

// Retry once after the user action.
await ext.rgApiV2.resources.revealAzureResource(azureResourceId, {
select: true,
focus: true,
expand: true,
});
}
}

function isSubscriptionNotFoundError(message: string): boolean {
const msg = message.toLowerCase();
// NOTE: This logic intentionally inspects the error message text instead of using a richer error
// type or code because the originating errors come from the Azure Resources / Azure Account
// extensions (via `ext.rgApiV2.resources.revealAzureResource`) and do not expose a stable,
// programmatic identifier for "subscription not found" conditions.
//
// Typical upstream messages that we have seen include variants such as:
// - "The subscription '...' could not be found."
// - "The subscription '...' does not exist."
// - "No subscriptions found."
//
// This helper is only used to decide when to show a specific "subscription not available with
// your current Azure sign-in" prompt and then retry once after the user signs in or selects
// subscriptions. If none of these patterns match, we rethrow the original error so that it is
// surfaced normally. To keep this heuristic conservative and avoid false positives, we require
// the presence of the word "subscription" together with a small set of "not found" phrases, or
// the exact "no subscriptions found" wording. If the Azure Resources / Azure Account layers
// change their error messages, this function may need to be updated accordingly.
return (
(msg.includes('subscription') && msg.includes('not found')) ||
(msg.includes('subscription') && msg.includes('could not be found')) ||
(msg.includes('subscription') && msg.includes('does not exist')) ||
msg.includes('no subscriptions found')
);
}

/**
* Creates and attaches a database connection to the workspace.
*
Expand Down
Loading
Loading