Skip to content

Commit e17d37e

Browse files
authored
Merge branch 'main' into dev/copy-and-paste/main
2 parents b8075cb + 9015f28 commit e17d37e

29 files changed

Lines changed: 600 additions & 56 deletions

.github/workflows/api-publish.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: "API: Publish to npm"
2+
3+
# 🚀 This workflow manually publishes the API package to npm.
4+
# It should only be triggered via GitHub's UI or CLI using `workflow_dispatch`.
5+
6+
on:
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.run_id }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
echo:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- run: echo "dummy action"

l10n/bundle.l10n.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"Connection String": "Connection String",
8686
"Connection string cannot be empty": "Connection string cannot be empty",
8787
"Connection string not found.": "Connection string not found.",
88+
"Connection updated successfully.": "Connection updated successfully.",
8889
"CosmosDB Accounts": "CosmosDB Accounts",
8990
"Could not find {0}": "Could not find {0}",
9091
"Could not find the Azure Resource Groups extension": "Could not find the Azure Resource Groups extension",
@@ -143,6 +144,7 @@
143144
"Enter the password": "Enter the password",
144145
"Enter the password for {experience}": "Enter the password for {experience}",
145146
"Enter the port number": "Enter the port number",
147+
"Enter the port number your DocumentDB uses. The default port: {defaultPort}.": "Enter the port number your DocumentDB uses. The default port: {defaultPort}.",
146148
"Enter the username": "Enter the username",
147149
"Enter the username for {experience}": "Enter the username for {experience}",
148150
"Error creating resource: {0}": "Error creating resource: {0}",
@@ -195,6 +197,7 @@
195197
"Failed to save credentials for \"{cluster}\".": "Failed to save credentials for \"{cluster}\".",
196198
"Failed to save credentials.": "Failed to save credentials.",
197199
"Failed to store secrets for key {0}:": "Failed to store secrets for key {0}:",
200+
"Failed to update the connection.": "Failed to update the connection.",
198201
"Failed with code \"{0}\".": "Failed with code \"{0}\".",
199202
"failed.": "failed.",
200203
"Find Query": "Find Query",
@@ -285,6 +288,7 @@
285288
"No session found for id {sessionId}": "No session found for id {sessionId}",
286289
"No subscriptions found": "No subscriptions found",
287290
"Not connected to any MongoDB database.": "Not connected to any MongoDB database.",
291+
"Note: This confirmation type can be configured in the settings (documentDB.userInterface.confirmationStyle).": "Note: This confirmation type can be configured in the settings (documentDB.userInterface.confirmationStyle).",
288292
"Open Collection": "Open Collection",
289293
"Open installation page": "Open installation page",
290294
"Opening DocumentDB resource…": "Opening DocumentDB resource…",
@@ -293,6 +297,7 @@
293297
"Pick \"{number}\" to confirm and continue.": "Pick \"{number}\" to confirm and continue.",
294298
"Please confirm by re-entering the previous value.": "Please confirm by re-entering the previous value.",
295299
"Please connect to a MongoDB database before running a Scrapbook command.": "Please connect to a MongoDB database before running a Scrapbook command.",
300+
"Please edit the connection string.": "Please edit the connection string.",
296301
"Please enter a new connection name.": "Please enter a new connection name.",
297302
"Please enter the password for the user \"{username}\"": "Please enter the password for the user \"{username}\"",
298303
"Please enter the username": "Please enter the username",
@@ -368,6 +373,7 @@
368373
"The connection with the name \"{0}\" already exists.": "The connection with the name \"{0}\" already exists.",
369374
"The custom cloud choice is not configured. Please configure the setting `{0}.{1}`.": "The custom cloud choice is not configured. Please configure the setting `{0}.{1}`.",
370375
"The database \"{0}\" already exists in the MongoDB Cluster \"{1}\".": "The database \"{0}\" already exists in the MongoDB Cluster \"{1}\".",
376+
"The default port: {defaultPort}": "The default port: {defaultPort}",
371377
"The default port: 10255": "The default port: 10255",
372378
"The document with the _id \"{0}\" has been saved.": "The document with the _id \"{0}\" has been saved.",
373379
"The entered value does not match the original.": "The entered value does not match the original.",
@@ -425,6 +431,7 @@
425431
"Up": "Up",
426432
"Update Azure Account Extension to at least version \"{0}\"...": "Update Azure Account Extension to at least version \"{0}\"...",
427433
"Update cluster credentials": "Update cluster credentials",
434+
"Update Connection String": "Update Connection String",
428435
"Update existing {accountType} connection?": "Update existing {accountType} connection?",
429436
"Updated entity \"{name}\".": "Updated entity \"{name}\".",
430437
"Upload": "Upload",
@@ -446,7 +453,7 @@
446453
"Yes": "Yes",
447454
"Yes, save my credentials": "Yes, save my credentials",
448455
"You are not signed in to an Azure account. Please sign in.": "You are not signed in to an Azure account. Please sign in.",
449-
"You are not signed in to Azure. Sign in to continue.": "You are not signed in to Azure. Sign in to continue.",
456+
"You are not signed in to Azure. Sign in and retry.": "You are not signed in to Azure. Sign in and retry.",
450457
"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.",
451458
"You can connect to a different DocumentDB by:": "You can connect to a different DocumentDB by:",
452459
"You do not have permission to create a resource group in subscription \"{0}\".": "You do not have permission to create a resource group in subscription \"{0}\".",

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,14 @@
275275
"icon": "$(key)"
276276
},
277277
{
278-
"//": "[ConnectionsView] Update Credentials",
278+
"//": "[ConnectionsView] Update Connection String",
279+
"category": "DocumentDB",
280+
"command": "vscode-documentdb.command.connectionsView.updateConnectionString",
281+
"title": "Update Connection String…",
282+
"icon": "$(whole-word)"
283+
},
284+
{
285+
"//": "[ConnectionsView] Rename Connection",
279286
"category": "DocumentDB",
280287
"command": "vscode-documentdb.command.connectionsView.renameConnection",
281288
"title": "Rename…",
@@ -509,20 +516,25 @@
509516
"editor/title": [],
510517
"view/item/context": [
511518
{
512-
"command": "vscode-documentdb.command.connectionsView.updateCredentials",
519+
"command": "vscode-documentdb.command.connectionsView.updateConnectionString",
513520
"when": "view == connectionsView && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i",
514521
"group": "0@2"
515522
},
516523
{
517-
"command": "vscode-documentdb.command.connectionsView.renameConnection",
524+
"command": "vscode-documentdb.command.connectionsView.updateCredentials",
518525
"when": "view == connectionsView && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i",
519526
"group": "0@3"
520527
},
528+
{
529+
"command": "vscode-documentdb.command.connectionsView.renameConnection",
530+
"when": "view == connectionsView && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i",
531+
"group": "0@4"
532+
},
521533
{
522534
"//": "Remove Connection...",
523535
"command": "vscode-documentdb.command.connectionsView.removeConnection",
524536
"when": "view == connectionsView && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i",
525-
"group": "0@4"
537+
"group": "0@5"
526538
},
527539
{
528540
"command": "vscode-documentdb.command.discoveryView.removeRegistry",
@@ -676,6 +688,10 @@
676688
"command": "vscode-documentdb.command.connectionsView.updateCredentials",
677689
"when": "never"
678690
},
691+
{
692+
"command": "vscode-documentdb.command.connectionsView.updateConnectionString",
693+
"when": "never"
694+
},
679695
{
680696
"command": "vscode-documentdb.command.connectionsView.removeConnection",
681697
"when": "never"
@@ -791,7 +807,7 @@
791807
"Prompts with a challenge where you select the correct number among multiple options to confirm deletion.",
792808
"Prompts with an input box where you enter the name of the resource to confirm deletion."
793809
],
794-
"default": "challengeConfirmation"
810+
"default": "wordConfirmation"
795811
},
796812
"documentDB.userInterface.ShowOperationSummaries": {
797813
"order": 1,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils';
7+
8+
import * as l10n from '@vscode/l10n';
9+
import { type UpdateCSWizardContext } from './UpdateCSWizardContext';
10+
11+
export class ConnectionStringStep extends AzureWizardPromptStep<UpdateCSWizardContext> {
12+
public async prompt(context: UpdateCSWizardContext): Promise<void> {
13+
const newConnectionString = await context.ui.showInputBox({
14+
prompt: l10n.t('Please edit the connection string.'),
15+
value: context.originalCS_NoCredentials,
16+
ignoreFocusOut: true,
17+
asyncValidationTask: (name: string) => this.validateConnectionString(name),
18+
});
19+
20+
context.newCS_NoCredentials = newConnectionString.trim();
21+
}
22+
23+
public shouldPrompt(): boolean {
24+
return true;
25+
}
26+
27+
private async validateConnectionString(connectionString: string): Promise<string | undefined> {
28+
connectionString = connectionString ? connectionString.trim() : '';
29+
30+
if (connectionString.length === 0) {
31+
// skip this for now, asyncValidationTask takes care of this case, otherwise it's only warnings the user sees..
32+
return undefined;
33+
}
34+
35+
if (!(connectionString.startsWith('mongodb://') || connectionString.startsWith('mongodb+srv://'))) {
36+
return l10n.t('"mongodb://" or "mongodb+srv://" must be the prefix of the connection string.');
37+
}
38+
39+
return undefined;
40+
}
41+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils';
7+
import { l10n, window } from 'vscode';
8+
import { StorageNames, StorageService } from '../../services/storageService';
9+
import { showConfirmationAsInSettings } from '../../utils/dialogs/showConfirmation';
10+
import { nonNullValue } from '../../utils/nonNull';
11+
import { type UpdateCSWizardContext } from './UpdateCSWizardContext';
12+
13+
export class ExecuteStep extends AzureWizardExecuteStep<UpdateCSWizardContext> {
14+
public priority: number = 100;
15+
16+
public async execute(context: UpdateCSWizardContext): Promise<void> {
17+
const resourceType = context.isEmulator ? 'emulators' : 'clusters';
18+
const storage = StorageService.get(StorageNames.Connections);
19+
const item = await storage.getItem(resourceType, context.storageId);
20+
21+
if (item) {
22+
if (!item.secrets) {
23+
item.secrets = [];
24+
}
25+
26+
// now, copy the credentials from the original connection string,
27+
// take it directly from the storage item
28+
// and update the new connection string with the credentials
29+
const originalCS_WithCredentials = new URL(item.secrets[0] || '');
30+
const newCS = new URL(context.newCS_NoCredentials || '');
31+
32+
newCS.username = originalCS_WithCredentials.username;
33+
newCS.password = originalCS_WithCredentials.password;
34+
35+
item.secrets[0] = nonNullValue(newCS.toString());
36+
37+
try {
38+
await storage.push(resourceType, item, true);
39+
} catch (pushError) {
40+
console.error(`Failed to update the item "${context.storageId}":`, pushError);
41+
void window.showErrorMessage(l10n.t('Failed to update the connection.'));
42+
}
43+
} else {
44+
console.error(`Item with ID "${context.storageId}" not found in storage.`);
45+
void window.showErrorMessage(l10n.t('Failed to update the connection.'));
46+
}
47+
48+
showConfirmationAsInSettings(l10n.t('Connection updated successfully.'));
49+
}
50+
51+
public shouldExecute(context: UpdateCSWizardContext): boolean {
52+
return !!context.newCS_NoCredentials && context.newCS_NoCredentials !== context.originalCS_NoCredentials;
53+
}
54+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type IActionContext } from '@microsoft/vscode-azext-utils';
7+
8+
export interface UpdateCSWizardContext extends IActionContext {
9+
// target item details
10+
isEmulator: boolean;
11+
storageId: string;
12+
13+
originalCS_NoCredentials: string;
14+
newCS_NoCredentials?: string;
15+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizard, type IActionContext } from '@microsoft/vscode-azext-utils';
7+
import * as l10n from '@vscode/l10n';
8+
import ConnectionString from 'mongodb-connection-string-url';
9+
import { Views } from '../../documentdb/Views';
10+
import { StorageNames, StorageService } from '../../services/storageService';
11+
import { type DocumentDBClusterItem } from '../../tree/connections-view/DocumentDBClusterItem';
12+
import { refreshView } from '../refreshView/refreshView';
13+
import { ConnectionStringStep } from './ConnectionStringStep';
14+
import { ExecuteStep } from './ExecuteStep';
15+
import { type UpdateCSWizardContext } from './UpdateCSWizardContext';
16+
17+
export async function updateConnectionString(context: IActionContext, node: DocumentDBClusterItem): Promise<void> {
18+
if (!node) {
19+
throw new Error(l10n.t('No node selected.'));
20+
}
21+
22+
// Extract the connection string but hide the username and password
23+
24+
// Note to future maintainers: the node.cluster might be out of date
25+
// as the object is cached in the tree view, and in the 'retry/error' nodes
26+
// that's why we need to get the fresh one each time.
27+
28+
const resourceType = node.cluster.emulatorConfiguration?.isEmulator ? 'emulators' : 'clusters';
29+
const storage = StorageService.get(StorageNames.Connections);
30+
const currentItem = await storage.getItem(resourceType, node.storageId);
31+
const connectionString = currentItem?.secrets?.[0] || '';
32+
context.valuesToMask.push(connectionString);
33+
const parsedCS = new ConnectionString(connectionString);
34+
35+
parsedCS.username = '';
36+
parsedCS.password = '';
37+
38+
const wizardContext: UpdateCSWizardContext = {
39+
...context,
40+
originalCS_NoCredentials: parsedCS.toString(),
41+
isEmulator: Boolean(node.cluster.emulatorConfiguration?.isEmulator),
42+
storageId: node.storageId,
43+
};
44+
45+
const wizard = new AzureWizard(wizardContext, {
46+
title: l10n.t('Update Connection String'),
47+
promptSteps: [new ConnectionStringStep()],
48+
executeSteps: [new ExecuteStep()],
49+
});
50+
51+
await wizard.prompt();
52+
await wizard.execute();
53+
54+
await refreshView(context, Views.ConnectionsView);
55+
}

src/commands/updateCredentials/updateCredentials.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { AzureWizard, nonNullValue, type IActionContext } from '@microsoft/vscode-azext-utils';
6+
import { AzureWizard, type IActionContext } from '@microsoft/vscode-azext-utils';
77
import * as l10n from '@vscode/l10n';
88
import ConnectionString from 'mongodb-connection-string-url';
99
import { ClustersClient } from '../../documentdb/ClustersClient';
1010
import { CredentialCache } from '../../documentdb/CredentialCache';
1111
import { Views } from '../../documentdb/Views';
12+
import { StorageNames, StorageService } from '../../services/storageService';
1213
import { type DocumentDBClusterItem } from '../../tree/connections-view/DocumentDBClusterItem';
1314
import { refreshView } from '../refreshView/refreshView';
1415
import { ExecuteStep } from './ExecuteStep';
@@ -22,9 +23,21 @@ export async function updateCredentials(context: IActionContext, node: DocumentD
2223
}
2324

2425
// access credentials assigned to the selected cluster
25-
const connectionString = new ConnectionString(nonNullValue(node.cluster.connectionString));
26-
const username: string | undefined = connectionString.username;
27-
const password: string | undefined = connectionString.password;
26+
27+
// Note to future maintainers: the node.cluster might be out of date
28+
// as the object is cached in the tree view, and in the 'retry/error' nodes
29+
// that's why we need to get the fresh one each time.
30+
31+
const resourceType = node.cluster.emulatorConfiguration?.isEmulator ? 'emulators' : 'clusters';
32+
const storage = StorageService.get(StorageNames.Connections);
33+
const currentItem = await storage.getItem(resourceType, node.storageId);
34+
const connectionString = currentItem?.secrets?.[0] || '';
35+
context.valuesToMask.push(connectionString);
36+
37+
const parsedCS = new ConnectionString(connectionString);
38+
39+
const username: string | undefined = parsedCS.username;
40+
const password: string | undefined = parsedCS.password;
2841

2942
const wizardContext: UpdateCredentialsWizardContext = {
3043
...context,

0 commit comments

Comments
 (0)