vCore: unified telemetry event names, removed obsolete telemetry calls (#2409)
vCore: - Resource Tracking: Introduced tracking for resource and workspace trees, with further updates and improvements. - Command Tracking: Enhanced command tracking. - Telemetry Cleanup: Removed outdated telemetry data. Shared: - Telemetry Unification: Consolidated telemetry for 'getChildren' events at the Cosmos DB account level.
This commit is contained in:
Родитель
6b7f7d86ce
Коммит
77681e4e9b
50
package.json
50
package.json
|
@ -589,52 +589,42 @@
|
|||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.hello",
|
||||
"title": "Dev: Say Hello!"
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.webview",
|
||||
"title": "Dev: Show WebView"
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.dropCollection",
|
||||
"command": "command.mongoClusters.dropCollection",
|
||||
"title": "Drop Collection..."
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.dropDatabase",
|
||||
"command": "command.mongoClusters.dropDatabase",
|
||||
"title": "Drop Database..."
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.createCollection",
|
||||
"command": "command.mongoClusters.createCollection",
|
||||
"title": "Create Collection..."
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.createDatabase",
|
||||
"command": "command.mongoClusters.createDatabase",
|
||||
"title": "Create Database..."
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.importDocuments",
|
||||
"command": "command.mongoClusters.importDocuments",
|
||||
"title": "Import Documents into Collection..."
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.exportDocuments",
|
||||
"command": "command.mongoClusters.exportDocuments",
|
||||
"title": "Export Documents from Collection..."
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.launchShell",
|
||||
"command": "command.mongoClusters.launchShell",
|
||||
"title": "Launch Shell"
|
||||
},
|
||||
{
|
||||
"category": "MongoDB (vCore)",
|
||||
"command": "mongoClusters.cmd.removeWorkspaceConnection",
|
||||
"command": "command.mongoClusters.removeWorkspaceConnection",
|
||||
"title": "Remove Connection..."
|
||||
}
|
||||
],
|
||||
|
@ -1135,38 +1125,38 @@
|
|||
"group": "1@1"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.dropCollection",
|
||||
"command": "command.mongoClusters.dropCollection",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem == mongoClusters.item.collection"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.dropDatabase",
|
||||
"command": "command.mongoClusters.dropDatabase",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem == mongoClusters.item.database"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.removeWorkspaceConnection",
|
||||
"command": "command.mongoClusters.removeWorkspaceConnection",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && view == azureWorkspace && viewItem == mongoClusters.item.mongoCluster"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.createCollection",
|
||||
"command": "command.mongoClusters.createCollection",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem == mongoClusters.item.database"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.createDatabase",
|
||||
"command": "command.mongoClusters.createDatabase",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem =~ /mongoClusters.item.mongoCluster/i",
|
||||
"group": "1@1"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.importDocuments",
|
||||
"command": "command.mongoClusters.importDocuments",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem == mongoClusters.item.collection",
|
||||
"group": "1@1"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.exportDocuments",
|
||||
"command": "command.mongoClusters.exportDocuments",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem == mongoClusters.item.collection",
|
||||
"group": "1@2"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.launchShell",
|
||||
"command": "command.mongoClusters.launchShell",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem =~ /mongoClusters.item.(mongoCluster|database|collection)/i",
|
||||
"group": "2@1"
|
||||
}
|
||||
|
@ -1213,14 +1203,6 @@
|
|||
{
|
||||
"command": "postgreSQL.executeQuery",
|
||||
"when": "editorLangId == 'postgres'"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.hello",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled"
|
||||
},
|
||||
{
|
||||
"command": "mongoClusters.cmd.webview",
|
||||
"when": "vscodeDatabases.mongoClustersSupportEnabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import { nonNullProp } from './utils/nonNull';
|
|||
|
||||
export enum API {
|
||||
MongoDB = 'MongoDB',
|
||||
MongoClusters = 'MongoClusters',
|
||||
Graph = 'Graph',
|
||||
Table = 'Table',
|
||||
Core = 'Core', // Now called NoSQL
|
||||
|
|
|
@ -13,11 +13,14 @@ import {
|
|||
type Resource,
|
||||
} from '@azure/cosmos';
|
||||
import {
|
||||
callWithTelemetryAndErrorHandling,
|
||||
type AzExtParentTreeItem,
|
||||
type AzExtTreeItem,
|
||||
type IActionContext,
|
||||
type ICreateChildImplContext,
|
||||
} from '@microsoft/vscode-azext-utils';
|
||||
import type * as vscode from 'vscode';
|
||||
import { API } from '../../AzureDBExperiences';
|
||||
import { type IDeleteWizardContext } from '../../commands/deleteDatabaseAccount/IDeleteWizardContext';
|
||||
import { deleteCosmosDBAccount } from '../../commands/deleteDatabaseAccount/deleteCosmosDBAccount';
|
||||
import { getThemeAgnosticIconPath, SERVERLESS_CAPABILITY_NAME } from '../../constants';
|
||||
|
@ -98,31 +101,55 @@ export abstract class DocDBAccountTreeItemBase extends DocDBTreeItemBase<Databas
|
|||
}
|
||||
|
||||
public async loadMoreChildrenImpl(clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
if (this.root.isEmulator) {
|
||||
const unableToReachEmulatorMessage: string =
|
||||
"Unable to reach emulator. Please ensure it is started and connected to the port specified by the 'cosmosDB.emulator.port' setting, then try again.";
|
||||
return await rejectOnTimeout(
|
||||
2000,
|
||||
() => super.loadMoreChildrenImpl(clearCache),
|
||||
unableToReachEmulatorMessage,
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
return await super.loadMoreChildrenImpl(clearCache);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && isRbacException(e) && !this.hasShownRbacNotification) {
|
||||
this.hasShownRbacNotification = true;
|
||||
const principalId = (await getSignedInPrincipalIdForAccountEndpoint(this.root.endpoint)) ?? '';
|
||||
// chedck if the principal ID matches the one that is signed in, otherwise this might be a security problem, hence show the error message
|
||||
if (e.message.includes(`[${principalId}]`) && (await ensureRbacPermission(this, principalId))) {
|
||||
const result = await callWithTelemetryAndErrorHandling(
|
||||
'getChildren',
|
||||
async (context: IActionContext): Promise<AzExtTreeItem[]> => {
|
||||
context.telemetry.properties.parentContext = this.contextValue;
|
||||
|
||||
// move this to a shared file, currently it's defined in DocDBAccountTreeItem so I can't reference it here
|
||||
if (this.contextValue.includes('cosmosDBDocumentServer')) {
|
||||
context.telemetry.properties.experience = API.Core;
|
||||
// same issue as above
|
||||
} else if (this.contextValue.includes('cosmosDBGraphAccount')) {
|
||||
context.telemetry.properties.experience = API.Graph;
|
||||
// same issue as above
|
||||
} else if (this.contextValue.includes('cosmosDBTableAccount')) {
|
||||
context.telemetry.properties.experience = API.Table;
|
||||
}
|
||||
|
||||
if (this.root.isEmulator) {
|
||||
const unableToReachEmulatorMessage: string =
|
||||
"Unable to reach emulator. Please ensure it is started and connected to the port specified by the 'cosmosDB.emulator.port' setting, then try again.";
|
||||
return await rejectOnTimeout(
|
||||
2000,
|
||||
() => super.loadMoreChildrenImpl(clearCache),
|
||||
unableToReachEmulatorMessage,
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
return await super.loadMoreChildrenImpl(clearCache);
|
||||
} else {
|
||||
void showRbacPermissionError(this.fullId, principalId);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && isRbacException(e) && !this.hasShownRbacNotification) {
|
||||
this.hasShownRbacNotification = true;
|
||||
const principalId =
|
||||
(await getSignedInPrincipalIdForAccountEndpoint(this.root.endpoint)) ?? '';
|
||||
// chedck if the principal ID matches the one that is signed in, otherwise this might be a security problem, hence show the error message
|
||||
if (
|
||||
e.message.includes(`[${principalId}]`) &&
|
||||
(await ensureRbacPermission(this, principalId))
|
||||
) {
|
||||
return await super.loadMoreChildrenImpl(clearCache);
|
||||
} else {
|
||||
void showRbacPermissionError(this.fullId, principalId);
|
||||
}
|
||||
}
|
||||
throw e; // rethrowing tells the resources extension to show the exception message in the tree
|
||||
}
|
||||
}
|
||||
throw e; // rethrowing tells the resources extension to show the exception message in the tree
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return result ?? [];
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(context: IDeleteWizardContext): Promise<void> {
|
||||
|
|
|
@ -7,12 +7,15 @@ import { type DatabaseAccountGetResults } from '@azure/arm-cosmosdb/src/models';
|
|||
import {
|
||||
appendExtensionUserAgent,
|
||||
AzExtParentTreeItem,
|
||||
callWithTelemetryAndErrorHandling,
|
||||
parseError,
|
||||
type AzExtTreeItem,
|
||||
type IActionContext,
|
||||
type ICreateChildImplContext,
|
||||
} from '@microsoft/vscode-azext-utils';
|
||||
import { type MongoClient } from 'mongodb';
|
||||
import type * as vscode from 'vscode';
|
||||
import { API } from '../../AzureDBExperiences';
|
||||
import { deleteCosmosDBAccount } from '../../commands/deleteDatabaseAccount/deleteCosmosDBAccount';
|
||||
import { type IDeleteWizardContext } from '../../commands/deleteDatabaseAccount/IDeleteWizardContext';
|
||||
import { getThemeAgnosticIconPath, Links, testDb } from '../../constants';
|
||||
|
@ -63,56 +66,70 @@ export class MongoAccountTreeItem extends AzExtParentTreeItem {
|
|||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
let mongoClient: MongoClient | undefined;
|
||||
try {
|
||||
let databases: IDatabaseInfo[];
|
||||
const result = await callWithTelemetryAndErrorHandling(
|
||||
'getChildren',
|
||||
async (context: IActionContext): Promise<AzExtTreeItem[]> => {
|
||||
context.telemetry.properties.experience = API.MongoDB;
|
||||
context.telemetry.properties.parentContext = this.contextValue;
|
||||
|
||||
if (!this.connectionString) {
|
||||
throw new Error('Missing connection string');
|
||||
}
|
||||
let mongoClient: MongoClient | undefined;
|
||||
try {
|
||||
let databases: IDatabaseInfo[];
|
||||
|
||||
// Azure MongoDB accounts need to have the name passed in for private endpoints
|
||||
mongoClient = await connectToMongoClient(
|
||||
this.connectionString,
|
||||
this.databaseAccount ? nonNullProp(this.databaseAccount, 'name') : appendExtensionUserAgent(),
|
||||
);
|
||||
if (!this.connectionString) {
|
||||
throw new Error('Missing connection string');
|
||||
}
|
||||
|
||||
const databaseInConnectionString = getDatabaseNameFromConnectionString(this.connectionString);
|
||||
if (databaseInConnectionString && !this.root.isEmulator) {
|
||||
// emulator violates the connection string format
|
||||
// If the database is in the connection string, that's all we connect to (we might not even have permissions to list databases)
|
||||
databases = [
|
||||
{
|
||||
name: databaseInConnectionString,
|
||||
empty: false,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
// https://mongodb.github.io/node-mongodb-native/3.1/api/index.html
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const result: { databases: IDatabaseInfo[] } = await mongoClient.db(testDb).admin().listDatabases();
|
||||
databases = result.databases;
|
||||
}
|
||||
return databases
|
||||
.filter(
|
||||
(database: IDatabaseInfo) =>
|
||||
!(database.name && database.name.toLowerCase() === 'admin' && database.empty),
|
||||
) // Filter out the 'admin' database if it's empty
|
||||
.map(
|
||||
(database) => new MongoDatabaseTreeItem(this, nonNullProp(database, 'name'), this.connectionString),
|
||||
);
|
||||
} catch (error) {
|
||||
const message = parseError(error).message;
|
||||
if (this.root?.isEmulator && message.includes('ECONNREFUSED')) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
error.message = `Unable to reach emulator. See ${Links.LocalConnectionDebuggingTips} for debugging tips.\n${message}`;
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
if (mongoClient) {
|
||||
void mongoClient.close();
|
||||
}
|
||||
}
|
||||
// Azure MongoDB accounts need to have the name passed in for private endpoints
|
||||
mongoClient = await connectToMongoClient(
|
||||
this.connectionString,
|
||||
this.databaseAccount ? nonNullProp(this.databaseAccount, 'name') : appendExtensionUserAgent(),
|
||||
);
|
||||
|
||||
const databaseInConnectionString = getDatabaseNameFromConnectionString(this.connectionString);
|
||||
if (databaseInConnectionString && !this.root.isEmulator) {
|
||||
// emulator violates the connection string format
|
||||
// If the database is in the connection string, that's all we connect to (we might not even have permissions to list databases)
|
||||
databases = [
|
||||
{
|
||||
name: databaseInConnectionString,
|
||||
empty: false,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
// https://mongodb.github.io/node-mongodb-native/3.1/api/index.html
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const result: { databases: IDatabaseInfo[] } = await mongoClient
|
||||
.db(testDb)
|
||||
.admin()
|
||||
.listDatabases();
|
||||
databases = result.databases;
|
||||
}
|
||||
return databases
|
||||
.filter(
|
||||
(database: IDatabaseInfo) =>
|
||||
!(database.name && database.name.toLowerCase() === 'admin' && database.empty),
|
||||
) // Filter out the 'admin' database if it's empty
|
||||
.map(
|
||||
(database) =>
|
||||
new MongoDatabaseTreeItem(this, nonNullProp(database, 'name'), this.connectionString),
|
||||
);
|
||||
} catch (error) {
|
||||
const message = parseError(error).message;
|
||||
if (this.root?.isEmulator && message.includes('ECONNREFUSED')) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
error.message = `Unable to reach emulator. See ${Links.LocalConnectionDebuggingTips} for debugging tips.\n${message}`;
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
if (mongoClient) {
|
||||
void mongoClient.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return result ?? [];
|
||||
}
|
||||
|
||||
public async createChildImpl(context: ICreateChildImplContext): Promise<MongoDatabaseTreeItem> {
|
||||
|
|
|
@ -43,85 +43,77 @@ export class MongoClustersExtension implements vscode.Disposable {
|
|||
}
|
||||
|
||||
async activate(): Promise<void> {
|
||||
await callWithTelemetryAndErrorHandling('mongoClusters.activate', async (activateContext: IActionContext) => {
|
||||
activateContext.telemetry.properties.isActivationEvent = 'true';
|
||||
await callWithTelemetryAndErrorHandling(
|
||||
'cosmosDB.mongoClusters.activate',
|
||||
async (activateContext: IActionContext) => {
|
||||
activateContext.telemetry.properties.isActivationEvent = 'true';
|
||||
|
||||
const isMongoClustersEnabled: boolean = isMongoClustersSupportenabled() ?? false;
|
||||
const isMongoClustersEnabled: boolean = isMongoClustersSupportenabled() ?? false;
|
||||
|
||||
activateContext.telemetry.properties.mongoClustersEnabled = isMongoClustersEnabled.toString();
|
||||
activateContext.telemetry.properties.mongoClustersEnabled = isMongoClustersEnabled.toString();
|
||||
|
||||
// allows to show/hide commands in the package.json file
|
||||
vscode.commands.executeCommand(
|
||||
'setContext',
|
||||
'vscodeDatabases.mongoClustersSupportEnabled',
|
||||
isMongoClustersEnabled,
|
||||
);
|
||||
// allows to show/hide commands in the package.json file
|
||||
vscode.commands.executeCommand(
|
||||
'setContext',
|
||||
'vscodeDatabases.mongoClustersSupportEnabled',
|
||||
isMongoClustersEnabled,
|
||||
);
|
||||
|
||||
if (!isMongoClustersEnabled) {
|
||||
return;
|
||||
}
|
||||
if (!isMongoClustersEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// // // MongoClusters / MongoDB (vCore) support is enabled // // //
|
||||
// // // MongoClusters / MongoDB (vCore) support is enabled // // //
|
||||
|
||||
ext.mongoClustersBranchDataProvider = new MongoClustersBranchDataProvider();
|
||||
ext.rgApiV2.resources.registerAzureResourceBranchDataProvider(
|
||||
AzExtResourceType.MongoClusters,
|
||||
ext.mongoClustersBranchDataProvider,
|
||||
);
|
||||
ext.mongoClustersBranchDataProvider = new MongoClustersBranchDataProvider();
|
||||
ext.rgApiV2.resources.registerAzureResourceBranchDataProvider(
|
||||
AzExtResourceType.MongoClusters,
|
||||
ext.mongoClustersBranchDataProvider,
|
||||
);
|
||||
|
||||
ext.workspaceDataProvider = new SharedWorkspaceResourceProvider();
|
||||
ext.rgApiV2.resources.registerWorkspaceResourceProvider(ext.workspaceDataProvider);
|
||||
ext.workspaceDataProvider = new SharedWorkspaceResourceProvider();
|
||||
ext.rgApiV2.resources.registerWorkspaceResourceProvider(ext.workspaceDataProvider);
|
||||
|
||||
ext.mongoClustersWorkspaceBranchDataProvider = new MongoClustersWorkspaceBranchDataProvider();
|
||||
ext.rgApiV2.resources.registerWorkspaceResourceBranchDataProvider(
|
||||
WorkspaceResourceType.MongoClusters,
|
||||
ext.mongoClustersWorkspaceBranchDataProvider,
|
||||
);
|
||||
ext.mongoClustersWorkspaceBranchDataProvider = new MongoClustersWorkspaceBranchDataProvider();
|
||||
ext.rgApiV2.resources.registerWorkspaceResourceBranchDataProvider(
|
||||
WorkspaceResourceType.MongoClusters,
|
||||
ext.mongoClustersWorkspaceBranchDataProvider,
|
||||
);
|
||||
|
||||
// using registerCommand instead of vscode.commands.registerCommand for better telemetry:
|
||||
// https://github.com/microsoft/vscode-azuretools/tree/main/utils#telemetry-and-error-handling
|
||||
registerCommand('mongoClusters.cmd.hello', this.commandSayHello);
|
||||
// using registerCommand instead of vscode.commands.registerCommand for better telemetry:
|
||||
// https://github.com/microsoft/vscode-azuretools/tree/main/utils#telemetry-and-error-handling
|
||||
|
||||
registerCommand('mongoClusters.internal.containerView.open', openCollectionView);
|
||||
registerCommand('mongoClusters.internal.documentView.open', openDocumentView);
|
||||
registerCommand('command.internal.mongoClusters.containerView.open', openCollectionView);
|
||||
registerCommand('command.internal.mongoClusters.documentView.open', openDocumentView);
|
||||
|
||||
registerCommandWithTreeNodeUnwrapping('mongoClusters.cmd.launchShell', launchShell);
|
||||
registerCommand('command.internal.mongoClusters.importDocuments', mongoClustersImportDocuments);
|
||||
registerCommand('command.internal.mongoClusters.exportDocuments', mongoClustersExportQueryResults);
|
||||
|
||||
registerCommandWithTreeNodeUnwrapping('mongoClusters.cmd.dropCollection', dropCollection);
|
||||
registerCommandWithTreeNodeUnwrapping('mongoClusters.cmd.dropDatabase', dropDatabase);
|
||||
registerCommandWithTreeNodeUnwrapping('command.mongoClusters.launchShell', launchShell);
|
||||
|
||||
registerCommandWithTreeNodeUnwrapping('mongoClusters.cmd.createCollection', createCollection);
|
||||
registerCommandWithTreeNodeUnwrapping('mongoClusters.cmd.createDatabase', createDatabase);
|
||||
registerCommandWithTreeNodeUnwrapping('command.mongoClusters.dropCollection', dropCollection);
|
||||
registerCommandWithTreeNodeUnwrapping('command.mongoClusters.dropDatabase', dropDatabase);
|
||||
|
||||
registerCommandWithTreeNodeUnwrapping('mongoClusters.cmd.importDocuments', mongoClustersImportDocuments);
|
||||
registerCommandWithTreeNodeUnwrapping(
|
||||
'mongoClusters.cmd.exportDocuments',
|
||||
mongoClustersExportEntireCollection,
|
||||
);
|
||||
registerCommandWithTreeNodeUnwrapping('command.mongoClusters.createCollection', createCollection);
|
||||
registerCommandWithTreeNodeUnwrapping('command.mongoClusters.createDatabase', createDatabase);
|
||||
|
||||
registerCommand('mongoClusters.internal.importDocuments', mongoClustersImportDocuments);
|
||||
registerCommand('mongoClusters.internal.exportDocuments', mongoClustersExportQueryResults);
|
||||
registerCommandWithTreeNodeUnwrapping(
|
||||
'command.mongoClusters.importDocuments',
|
||||
mongoClustersImportDocuments,
|
||||
);
|
||||
registerCommandWithTreeNodeUnwrapping(
|
||||
'command.mongoClusters.exportDocuments',
|
||||
mongoClustersExportEntireCollection,
|
||||
);
|
||||
|
||||
registerCommand('mongoClusters.cmd.addWorkspaceConnection', addWorkspaceConnection);
|
||||
registerCommandWithTreeNodeUnwrapping(
|
||||
'mongoClusters.cmd.removeWorkspaceConnection',
|
||||
removeWorkspaceConnection,
|
||||
);
|
||||
registerCommand('command.mongoClusters.addWorkspaceConnection', addWorkspaceConnection);
|
||||
registerCommandWithTreeNodeUnwrapping(
|
||||
'command.mongoClusters.removeWorkspaceConnection',
|
||||
removeWorkspaceConnection,
|
||||
);
|
||||
|
||||
ext.outputChannel.appendLine(`mongoClusters: activated.`);
|
||||
});
|
||||
}
|
||||
|
||||
// commands
|
||||
|
||||
commandSayHello = (): void => {
|
||||
console.log(`Hello there here!!!`);
|
||||
void vscode.window.showInformationMessage('Saying hello here!');
|
||||
|
||||
void vscode.window.showWarningMessage(
|
||||
`Are you sure?`,
|
||||
{ modal: true, detail: "You are about to:\n\ndelete 5 documents.\n\nThis action can't be undone." },
|
||||
'Delete',
|
||||
ext.outputChannel.appendLine(`MongoDB Clusters: activated.`);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
AzureWizard,
|
||||
callWithTelemetryAndErrorHandling,
|
||||
UserCancelledError,
|
||||
type IActionContext,
|
||||
} from '@microsoft/vscode-azext-utils';
|
||||
import { AzureWizard, UserCancelledError, type IActionContext } from '@microsoft/vscode-azext-utils';
|
||||
import ConnectionString from 'mongodb-connection-string-url';
|
||||
import * as vscode from 'vscode';
|
||||
import { API } from '../../AzureDBExperiences';
|
||||
|
@ -29,25 +24,20 @@ export async function addWorkspaceConnection(context: IActionContext): Promise<v
|
|||
promptSteps: [new ConnectionStringStep(), new UsernameStep(), new PasswordStep()],
|
||||
});
|
||||
|
||||
// Prompt the user for credentials
|
||||
await callWithTelemetryAndErrorHandling(
|
||||
'mongoClusters.addWorkspaceConnection.promptForCredentials',
|
||||
async (context: IActionContext) => {
|
||||
context.errorHandling.rethrow = true;
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
try {
|
||||
await wizard.prompt();
|
||||
} catch (error) {
|
||||
if (error instanceof UserCancelledError) {
|
||||
// The user cancelled the wizard
|
||||
wizardContext.aborted = true;
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
context.errorHandling.rethrow = true;
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
|
||||
try {
|
||||
await wizard.prompt();
|
||||
} catch (error) {
|
||||
if (error instanceof UserCancelledError) {
|
||||
// The user cancelled the wizard
|
||||
wizardContext.aborted = true;
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (wizardContext.aborted) {
|
||||
return;
|
||||
|
|
|
@ -33,7 +33,7 @@ export class CollectionItem {
|
|||
contextValue: 'mongoClusters.item.documents',
|
||||
id: `${this.id}/documents`,
|
||||
label: 'Documents',
|
||||
commandId: 'mongoClusters.internal.containerView.open',
|
||||
commandId: 'command.internal.mongoClusters.containerView.open',
|
||||
commandArgs: [
|
||||
{
|
||||
id: this.id,
|
||||
|
|
|
@ -34,7 +34,7 @@ export class DatabaseItem {
|
|||
id: `${this.id}/no-databases`,
|
||||
label: 'Create collection...',
|
||||
iconPath: new vscode.ThemeIcon('plus'),
|
||||
commandId: 'mongoClusters.cmd.createCollection',
|
||||
commandId: 'command.mongoClusters.createCollection',
|
||||
commandArgs: [this],
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -38,6 +38,7 @@ export class IndexItem {
|
|||
getTreeItem(): TreeItem {
|
||||
return {
|
||||
id: this.id,
|
||||
contextValue: 'mongoClusters.item.index',
|
||||
label: this.indexInfo.name,
|
||||
iconPath: new ThemeIcon('combine'), // TODO: create our onw icon here, this one's shape can change
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
|
|
|
@ -31,6 +31,7 @@ export class IndexesItem {
|
|||
getTreeItem(): TreeItem {
|
||||
return {
|
||||
id: this.id,
|
||||
contextValue: 'mongoClusters.item.indexes',
|
||||
label: 'Indexes',
|
||||
iconPath: new ThemeIcon('combine'), // TODO: create our onw icon here, this one's shape can change
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
callWithTelemetryAndErrorHandling,
|
||||
createGenericElement,
|
||||
type IActionContext,
|
||||
type TreeElementBase,
|
||||
} from '@microsoft/vscode-azext-utils';
|
||||
import { createGenericElement, type IActionContext, type TreeElementBase } from '@microsoft/vscode-azext-utils';
|
||||
import { type TreeItem } from 'vscode';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
@ -38,7 +33,7 @@ export abstract class MongoClusterItemBase implements TreeElementBase {
|
|||
* @param context The action context.
|
||||
* @returns An instance of MongoClustersClient if successful; otherwise, null.
|
||||
*/
|
||||
protected abstract authenticateAndConnect(context: IActionContext): Promise<MongoClustersClient | null>;
|
||||
protected abstract authenticateAndConnect(): Promise<MongoClustersClient | null>;
|
||||
|
||||
/**
|
||||
* Authenticates and connects to the cluster to list all available databases.
|
||||
|
@ -54,66 +49,52 @@ export abstract class MongoClusterItemBase implements TreeElementBase {
|
|||
* @returns A list of databases in the cluster or a single element to create a new database.
|
||||
*/
|
||||
async getChildren(): Promise<TreeElementBase[]> {
|
||||
const result = await callWithTelemetryAndErrorHandling(
|
||||
'mongoClusterItem.getChildren',
|
||||
async (context: IActionContext) => {
|
||||
// Error handling setup
|
||||
context.errorHandling.suppressDisplay = false;
|
||||
context.errorHandling.rethrow = true;
|
||||
context.valuesToMask.push(this.id, this.mongoCluster.name);
|
||||
ext.outputChannel.appendLine(`MongoDB Clusters: Loading cluster details for "${this.mongoCluster.name}"`);
|
||||
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Loading cluster details for "${this.mongoCluster.name}"`,
|
||||
);
|
||||
let mongoClustersClient: MongoClustersClient | null;
|
||||
|
||||
let mongoClustersClient: MongoClustersClient | null;
|
||||
// Check if credentials are cached, and return the cached client if available
|
||||
if (CredentialCache.hasCredentials(this.id)) {
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Reusing active connection for "${this.mongoCluster.name}".`,
|
||||
);
|
||||
mongoClustersClient = await MongoClustersClient.getClient(this.id);
|
||||
} else {
|
||||
// Call to the abstract method to authenticate and connect to the cluster
|
||||
mongoClustersClient = await this.authenticateAndConnect();
|
||||
}
|
||||
|
||||
// Check if credentials are cached, and return the cached client if available
|
||||
if (CredentialCache.hasCredentials(this.id)) {
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Reusing active connection for "${this.mongoCluster.name}".`,
|
||||
);
|
||||
mongoClustersClient = await MongoClustersClient.getClient(this.id);
|
||||
} else {
|
||||
// Call to the abstract method to authenticate and connect to the cluster
|
||||
mongoClustersClient = await this.authenticateAndConnect(context);
|
||||
}
|
||||
// If authentication failed, return the error element
|
||||
if (!mongoClustersClient) {
|
||||
return [
|
||||
createGenericElement({
|
||||
contextValue: 'error',
|
||||
id: `${this.id}/error`,
|
||||
label: 'Failed to authenticate (click to retry)',
|
||||
iconPath: new vscode.ThemeIcon('error'),
|
||||
commandId: 'azureResourceGroups.refreshTree',
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
// If authentication failed, return the error element
|
||||
if (!mongoClustersClient) {
|
||||
return [
|
||||
createGenericElement({
|
||||
contextValue: 'error',
|
||||
id: `${this.id}/error`,
|
||||
label: 'Failed to authenticate (click to retry)',
|
||||
iconPath: new vscode.ThemeIcon('error'),
|
||||
commandId: 'azureResourceGroups.refreshTree',
|
||||
}),
|
||||
];
|
||||
}
|
||||
// List the databases
|
||||
return mongoClustersClient.listDatabases().then((databases: DatabaseItemModel[]) => {
|
||||
if (databases.length === 0) {
|
||||
return [
|
||||
createGenericElement({
|
||||
contextValue: 'mongoClusters.item.no-databases',
|
||||
id: `${this.id}/no-databases`,
|
||||
label: 'Create database...',
|
||||
iconPath: new vscode.ThemeIcon('plus'),
|
||||
commandId: 'command.mongoClusters.createDatabase',
|
||||
commandArgs: [this],
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
// List the databases
|
||||
return mongoClustersClient.listDatabases().then((databases: DatabaseItemModel[]) => {
|
||||
if (databases.length === 0) {
|
||||
return [
|
||||
createGenericElement({
|
||||
contextValue: 'mongoClusters.item.no-databases',
|
||||
id: `${this.id}/no-databases`,
|
||||
label: 'Create database...',
|
||||
iconPath: new vscode.ThemeIcon('plus'),
|
||||
commandId: 'mongoClusters.cmd.createDatabase',
|
||||
commandArgs: [this],
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
// Map the databases to DatabaseItem elements
|
||||
return databases.map((database) => new DatabaseItem(this.mongoCluster, database));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return result ?? [];
|
||||
// Map the databases to DatabaseItem elements
|
||||
return databases.map((database) => new DatabaseItem(this.mongoCluster, database));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -40,80 +40,89 @@ export class MongoClusterResourceItem extends MongoClusterItemBase {
|
|||
* @param context The action context.
|
||||
* @returns An instance of MongoClustersClient if successful; otherwise, null.
|
||||
*/
|
||||
protected async authenticateAndConnect(context: IActionContext): Promise<MongoClustersClient | null> {
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Attempting to authenticate with "${this.mongoCluster.name}"...`,
|
||||
protected async authenticateAndConnect(): Promise<MongoClustersClient | null> {
|
||||
const result = await callWithTelemetryAndErrorHandling(
|
||||
'cosmosDB.mongoClusters.authenticate',
|
||||
async (context: IActionContext) => {
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Attempting to authenticate with "${this.mongoCluster.name}"...`,
|
||||
);
|
||||
|
||||
// Create a client to interact with the MongoDB vCore management API and read the cluster details
|
||||
const managementClient = await createMongoClustersManagementClient(context, this.subscription);
|
||||
const clusterInformation = await managementClient.mongoClusters.get(
|
||||
this.mongoCluster.resourceGroup as string,
|
||||
this.mongoCluster.name,
|
||||
);
|
||||
|
||||
const clusterConnectionString = nonNullValue(clusterInformation.connectionString);
|
||||
|
||||
context.valuesToMask.push(clusterConnectionString);
|
||||
|
||||
// Fetch non-admin users using the extracted method
|
||||
const clusterNonAdminUsers = await this.fetchNonAdminUsersFromAzure(
|
||||
managementClient,
|
||||
clusterInformation,
|
||||
);
|
||||
|
||||
const wizardContext: AuthenticateWizardContext = {
|
||||
...context,
|
||||
adminUserName: clusterInformation.administratorLogin,
|
||||
otherUserNames: clusterNonAdminUsers,
|
||||
resourceName: this.mongoCluster.name,
|
||||
};
|
||||
|
||||
// Prompt the user for credentials
|
||||
const credentialsProvided = await this.promptForCredentials(wizardContext);
|
||||
|
||||
// If the wizard was aborted or failed, return null
|
||||
if (!credentialsProvided) {
|
||||
return null;
|
||||
}
|
||||
|
||||
context.valuesToMask.push(nonNullProp(wizardContext, 'password'));
|
||||
|
||||
// Cache the credentials
|
||||
CredentialCache.setCredentials(
|
||||
this.id,
|
||||
nonNullValue(clusterConnectionString),
|
||||
nonNullProp(wizardContext, 'selectedUserName'),
|
||||
nonNullProp(wizardContext, 'password'),
|
||||
);
|
||||
|
||||
ext.outputChannel.append(
|
||||
`MongoDB Clusters: Connecting to the cluster as "${wizardContext.selectedUserName}"... `,
|
||||
);
|
||||
|
||||
// Attempt to create the client with the provided credentials
|
||||
let mongoClustersClient: MongoClustersClient;
|
||||
try {
|
||||
mongoClustersClient = await MongoClustersClient.getClient(this.id).catch((error: Error) => {
|
||||
ext.outputChannel.appendLine(`Error: ${error.message}`);
|
||||
|
||||
void vscode.window.showErrorMessage(`Failed to connect: ${error.message}`);
|
||||
|
||||
throw error;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// If connection fails, remove cached credentials
|
||||
await MongoClustersClient.deleteClient(this.id);
|
||||
CredentialCache.deleteCredentials(this.id);
|
||||
|
||||
// Return null to indicate failure
|
||||
return null;
|
||||
}
|
||||
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Connected to "${this.mongoCluster.name}" as "${wizardContext.selectedUserName}".`,
|
||||
);
|
||||
|
||||
return mongoClustersClient;
|
||||
},
|
||||
);
|
||||
|
||||
// Create a client to interact with the MongoDB vCore management API and read the cluster details
|
||||
const managementClient = await createMongoClustersManagementClient(context, this.subscription);
|
||||
const clusterInformation = await managementClient.mongoClusters.get(
|
||||
this.mongoCluster.resourceGroup as string,
|
||||
this.mongoCluster.name,
|
||||
);
|
||||
|
||||
const clusterConnectionString = nonNullValue(clusterInformation.connectionString);
|
||||
|
||||
context.valuesToMask.push(clusterConnectionString);
|
||||
|
||||
// Fetch non-admin users using the extracted method
|
||||
const clusterNonAdminUsers = await this.fetchNonAdminUsersFromAzure(managementClient, clusterInformation);
|
||||
|
||||
const wizardContext: AuthenticateWizardContext = {
|
||||
...context,
|
||||
adminUserName: clusterInformation.administratorLogin,
|
||||
otherUserNames: clusterNonAdminUsers,
|
||||
resourceName: this.mongoCluster.name,
|
||||
};
|
||||
|
||||
// Prompt the user for credentials
|
||||
const credentialsProvided = await this.promptForCredentials(wizardContext);
|
||||
|
||||
// If the wizard was aborted or failed, return null
|
||||
if (!credentialsProvided) {
|
||||
return null;
|
||||
}
|
||||
|
||||
context.valuesToMask.push(nonNullProp(wizardContext, 'password'));
|
||||
|
||||
// Cache the credentials
|
||||
CredentialCache.setCredentials(
|
||||
this.id,
|
||||
nonNullValue(clusterConnectionString),
|
||||
nonNullProp(wizardContext, 'selectedUserName'),
|
||||
nonNullProp(wizardContext, 'password'),
|
||||
);
|
||||
|
||||
ext.outputChannel.append(
|
||||
`MongoDB Clusters: Connecting to the cluster as "${wizardContext.selectedUserName}"... `,
|
||||
);
|
||||
|
||||
// Attempt to create the client with the provided credentials
|
||||
let mongoClustersClient: MongoClustersClient;
|
||||
try {
|
||||
mongoClustersClient = await MongoClustersClient.getClient(this.id).catch((error: Error) => {
|
||||
ext.outputChannel.appendLine('failed.');
|
||||
ext.outputChannel.appendLine(`Error: ${error.message}`);
|
||||
|
||||
void vscode.window.showErrorMessage(`Failed to connect: ${error.message}`);
|
||||
|
||||
throw error;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// If connection fails, remove cached credentials
|
||||
await MongoClustersClient.deleteClient(this.id);
|
||||
CredentialCache.deleteCredentials(this.id);
|
||||
|
||||
// Return null to indicate failure
|
||||
return null;
|
||||
}
|
||||
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Connected to "${this.mongoCluster.name}" as "${wizardContext.selectedUserName}".`,
|
||||
);
|
||||
|
||||
return mongoClustersClient;
|
||||
return result ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,20 +139,14 @@ export class MongoClusterResourceItem extends MongoClusterItemBase {
|
|||
});
|
||||
|
||||
// Prompt the user for credentials
|
||||
await callWithTelemetryAndErrorHandling(
|
||||
'mongoClusterItem.authenticate.promptForCredentials',
|
||||
async (_context: IActionContext) => {
|
||||
_context.errorHandling.rethrow = true;
|
||||
_context.errorHandling.suppressDisplay = false;
|
||||
try {
|
||||
await wizard.prompt(); // This will prompt the user; results are stored in wizardContext
|
||||
} catch (error) {
|
||||
if (error instanceof UserCancelledError) {
|
||||
wizardContext.aborted = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await wizard.prompt(); // This will prompt the user; results are stored in wizardContext
|
||||
} catch (error) {
|
||||
if (error instanceof UserCancelledError) {
|
||||
wizardContext.aborted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if the wizard completed successfully; false otherwise
|
||||
return !wizardContext.aborted;
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
type ResourceModelBase,
|
||||
} from '@microsoft/vscode-azureresources-api';
|
||||
import * as vscode from 'vscode';
|
||||
import { API } from '../../AzureDBExperiences';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { createMongoClustersManagementClient } from '../../utils/azureClients';
|
||||
import { type MongoClusterModel } from './MongoClusterModel';
|
||||
|
@ -49,13 +50,18 @@ export class MongoClustersBranchDataProvider
|
|||
/**
|
||||
* getChildren is called for every element in the tree when expanding, the element being expanded is being passed as an argument
|
||||
*/
|
||||
return (await element.getChildren?.())?.map((child) => {
|
||||
if (child.id) {
|
||||
return ext.state.wrapItemInStateHandling(child as TreeElementBase & { id: string }, () =>
|
||||
this.refresh(child),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
return await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => {
|
||||
context.telemetry.properties.experience = API.MongoClusters;
|
||||
context.telemetry.properties.parentContext = (await element.getTreeItem()).contextValue ?? 'unknown';
|
||||
|
||||
return (await element.getChildren?.())?.map((child) => {
|
||||
if (child.id) {
|
||||
return ext.state.wrapItemInStateHandling(child as TreeElementBase & { id: string }, () =>
|
||||
this.refresh(child),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -65,12 +71,14 @@ export class MongoClustersBranchDataProvider
|
|||
*/
|
||||
|
||||
const resourceItem = await callWithTelemetryAndErrorHandling(
|
||||
'mongoCluster.getResourceItem',
|
||||
'resolveResource',
|
||||
// disabling require-await, the async aspect is in there, but uses the .then pattern
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async (_context: IActionContext) => {
|
||||
async (context: IActionContext) => {
|
||||
context.telemetry.properties.experience = API.MongoClusters;
|
||||
|
||||
if (this.detailsCacheUpdateRequested) {
|
||||
void this.updateResourceCache(_context, element.subscription, 1000 * 60 * 5).then(() => {
|
||||
void this.updateResourceCache(context, element.subscription, 1000 * 60 * 5).then(() => {
|
||||
/**
|
||||
* Instances of MongoClusterItem were stored in the itemsToUpdateInfo map,
|
||||
* so that when the cache is updated, the items can be refreshed.
|
||||
|
@ -118,9 +126,11 @@ export class MongoClustersBranchDataProvider
|
|||
cacheDuration: number,
|
||||
): Promise<void> {
|
||||
return callWithTelemetryAndErrorHandling(
|
||||
'mongoClusters.getResourceItem.cacheUpdate',
|
||||
'resolveResource.updatingResourceCache',
|
||||
async (context: IActionContext) => {
|
||||
try {
|
||||
context.telemetry.properties.experience = API.MongoClusters;
|
||||
|
||||
this.detailsCacheUpdateRequested = false;
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -35,70 +35,82 @@ export class MongoClusterWorkspaceItem extends MongoClusterItemBase {
|
|||
* @param context The action context.
|
||||
* @returns An instance of MongoClustersClient if successful; otherwise, null.
|
||||
*/
|
||||
protected async authenticateAndConnect(context: IActionContext): Promise<MongoClustersClient | null> {
|
||||
ext.outputChannel.appendLine(`MongoDB Clusters: Attempting to authenticate with ${this.mongoCluster.name}`);
|
||||
protected async authenticateAndConnect(): Promise<MongoClustersClient | null> {
|
||||
const result = await callWithTelemetryAndErrorHandling(
|
||||
'cosmosDB.mongoClusters.authenticate',
|
||||
async (context: IActionContext) => {
|
||||
context.telemetry.properties.view = 'workspace';
|
||||
|
||||
let mongoClustersClient: MongoClustersClient;
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Attempting to authenticate with ${this.mongoCluster.name}`,
|
||||
);
|
||||
|
||||
const connectionString = new ConnectionString(nonNullValue(this.mongoCluster.connectionString));
|
||||
let mongoClustersClient: MongoClustersClient;
|
||||
|
||||
let username: string | undefined = connectionString.username;
|
||||
let password: string | undefined = connectionString.password;
|
||||
const connectionString = new ConnectionString(nonNullValue(this.mongoCluster.connectionString));
|
||||
|
||||
if (!username || username.length === 0 || !password || password.length === 0) {
|
||||
const wizardContext: AuthenticateWizardContext = {
|
||||
...context,
|
||||
adminUserName: undefined,
|
||||
otherUserNames: [],
|
||||
resourceName: this.mongoCluster.name,
|
||||
let username: string | undefined = connectionString.username;
|
||||
let password: string | undefined = connectionString.password;
|
||||
|
||||
// preconfigure the username in case it's provided connection string
|
||||
selectedUserName: username,
|
||||
// we'll always ask for the password
|
||||
};
|
||||
if (!username || username.length === 0 || !password || password.length === 0) {
|
||||
const wizardContext: AuthenticateWizardContext = {
|
||||
...context,
|
||||
adminUserName: undefined,
|
||||
otherUserNames: [],
|
||||
resourceName: this.mongoCluster.name,
|
||||
|
||||
// Prompt the user for credentials using the extracted method
|
||||
const credentialsProvided = await this.promptForCredentials(wizardContext);
|
||||
// preconfigure the username in case it's provided connection string
|
||||
selectedUserName: username,
|
||||
// we'll always ask for the password
|
||||
};
|
||||
|
||||
// If the wizard was aborted or failed, return null
|
||||
if (!credentialsProvided) {
|
||||
return null;
|
||||
}
|
||||
// Prompt the user for credentials using the extracted method
|
||||
const credentialsProvided = await this.promptForCredentials(wizardContext);
|
||||
|
||||
context.valuesToMask.push(nonNullProp(wizardContext, 'password'));
|
||||
// If the wizard was aborted or failed, return null
|
||||
if (!credentialsProvided) {
|
||||
return null;
|
||||
}
|
||||
|
||||
username = nonNullProp(wizardContext, 'selectedUserName');
|
||||
password = nonNullProp(wizardContext, 'password');
|
||||
}
|
||||
context.valuesToMask.push(nonNullProp(wizardContext, 'password'));
|
||||
|
||||
ext.outputChannel.append(`MongoDB Clusters: Connecting to the cluster as "${username}"... `);
|
||||
username = nonNullProp(wizardContext, 'selectedUserName');
|
||||
password = nonNullProp(wizardContext, 'password');
|
||||
}
|
||||
|
||||
// Cache the credentials
|
||||
CredentialCache.setCredentials(this.id, connectionString.toString(), username, password);
|
||||
ext.outputChannel.append(`MongoDB Clusters: Connecting to the cluster as "${username}"... `);
|
||||
|
||||
// Attempt to create the client with the provided credentials
|
||||
try {
|
||||
mongoClustersClient = await MongoClustersClient.getClient(this.id).catch((error: Error) => {
|
||||
ext.outputChannel.appendLine('failed.');
|
||||
ext.outputChannel.appendLine(`Error: ${error.message}`);
|
||||
// Cache the credentials
|
||||
CredentialCache.setCredentials(this.id, connectionString.toString(), username, password);
|
||||
|
||||
void vscode.window.showErrorMessage(`Failed to connect: ${error.message}`);
|
||||
// Attempt to create the client with the provided credentials
|
||||
try {
|
||||
mongoClustersClient = await MongoClustersClient.getClient(this.id).catch((error: Error) => {
|
||||
ext.outputChannel.appendLine('failed.');
|
||||
ext.outputChannel.appendLine(`Error: ${error.message}`);
|
||||
|
||||
throw error;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// If connection fails, remove cached credentials
|
||||
await MongoClustersClient.deleteClient(this.id);
|
||||
CredentialCache.deleteCredentials(this.id);
|
||||
void vscode.window.showErrorMessage(`Failed to connect: ${error.message}`);
|
||||
|
||||
// Return null to indicate failure
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// If connection fails, remove cached credentials
|
||||
await MongoClustersClient.deleteClient(this.id);
|
||||
CredentialCache.deleteCredentials(this.id);
|
||||
|
||||
ext.outputChannel.appendLine(`MongoDB Clusters: Connected to "${this.mongoCluster.name}" as "${username}"`);
|
||||
// Return null to indicate failure
|
||||
return null;
|
||||
}
|
||||
|
||||
return mongoClustersClient;
|
||||
ext.outputChannel.appendLine(
|
||||
`MongoDB Clusters: Connected to "${this.mongoCluster.name}" as "${username}"`,
|
||||
);
|
||||
|
||||
return mongoClustersClient;
|
||||
},
|
||||
);
|
||||
return result ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,10 +127,12 @@ export class MongoClusterWorkspaceItem extends MongoClusterItemBase {
|
|||
|
||||
// Prompt the user for credentials
|
||||
await callWithTelemetryAndErrorHandling(
|
||||
'mongoClusterItem.authenticate.promptForCredentials',
|
||||
async (_context: IActionContext) => {
|
||||
_context.errorHandling.rethrow = true;
|
||||
_context.errorHandling.suppressDisplay = false;
|
||||
'cosmosDB.mongoClusters.authenticate.promptForCredentials',
|
||||
async (context: IActionContext) => {
|
||||
context.telemetry.properties.view = 'workspace';
|
||||
|
||||
context.errorHandling.rethrow = true;
|
||||
context.errorHandling.suppressDisplay = false;
|
||||
try {
|
||||
await wizard.prompt(); // This will prompt the user; results are stored in wizardContext
|
||||
} catch (error) {
|
||||
|
|
|
@ -3,10 +3,15 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { type TreeElementBase } from '@microsoft/vscode-azext-utils';
|
||||
import {
|
||||
callWithTelemetryAndErrorHandling,
|
||||
type IActionContext,
|
||||
type TreeElementBase,
|
||||
} from '@microsoft/vscode-azext-utils';
|
||||
import { type WorkspaceResourceBranchDataProvider } from '@microsoft/vscode-azureresources-api';
|
||||
import * as vscode from 'vscode';
|
||||
import { type TreeItem } from 'vscode';
|
||||
import { API } from '../../../AzureDBExperiences';
|
||||
import { ext } from '../../../extensionVariables';
|
||||
import { MongoDBAccountsWorkspaceItem } from './MongoDBAccountsWorkspaceItem';
|
||||
|
||||
|
@ -27,13 +32,19 @@ export class MongoClustersWorkspaceBranchDataProvider
|
|||
}
|
||||
|
||||
async getChildren(element: TreeElementBase): Promise<TreeElementBase[] | null | undefined> {
|
||||
return (await element.getChildren?.())?.map((child) => {
|
||||
if (child.id) {
|
||||
return ext.state.wrapItemInStateHandling(child as TreeElementBase & { id: string }, () =>
|
||||
this.refresh(child),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
return await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => {
|
||||
context.telemetry.properties.experience = API.MongoClusters;
|
||||
context.telemetry.properties.view = 'workspace';
|
||||
context.telemetry.properties.parentContext = (await element.getTreeItem()).contextValue ?? 'unknown';
|
||||
|
||||
return (await element.getChildren?.())?.map((child) => {
|
||||
if (child.id) {
|
||||
return ext.state.wrapItemInStateHandling(child as TreeElementBase & { id: string }, () =>
|
||||
this.refresh(child),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export class MongoDBAccountsWorkspaceItem implements TreeElementBase {
|
|||
id: string;
|
||||
|
||||
constructor() {
|
||||
this.id = `vscode.cosmosdb.workspace.mongoclusters.mongodbaccounts`;
|
||||
this.id = `vscode.cosmosdb.workspace.mongoclusters.accounts`;
|
||||
}
|
||||
|
||||
async getChildren(): Promise<TreeElementBase[]> {
|
||||
|
@ -34,7 +34,7 @@ export class MongoDBAccountsWorkspaceItem implements TreeElementBase {
|
|||
id: this.id + '/newConnection',
|
||||
label: 'New Connection...',
|
||||
iconPath: new ThemeIcon('plus'),
|
||||
commandId: 'mongoClusters.cmd.addWorkspaceConnection',
|
||||
commandId: 'command.mongoClusters.addWorkspaceConnection',
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ export class MongoDBAccountsWorkspaceItem implements TreeElementBase {
|
|||
getTreeItem(): TreeItem {
|
||||
return {
|
||||
id: this.id,
|
||||
contextValue: 'vscode.cosmosdb.workspace.mongoclusters.mongodbaccounts',
|
||||
contextValue: 'vscode.cosmosdb.workspace.mongoclusters.accounts',
|
||||
label: 'MongoDB Cluster Accounts',
|
||||
iconPath: new ThemeIcon('link'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
|
|
|
@ -35,8 +35,7 @@ export class ConnectionStringStep extends AzureWizardPromptStep<AddWorkspaceConn
|
|||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
private async validateConnectionString(connectionString: string): Promise<string | null | undefined> {
|
||||
try {
|
||||
const parsedCS = new ConnectionString(connectionString);
|
||||
console.log(parsedCS);
|
||||
new ConnectionString(connectionString);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'MongoParseError') {
|
||||
return error.message;
|
||||
|
|
|
@ -113,7 +113,7 @@ export const collectionsViewRouter = router({
|
|||
.mutation(({ ctx }) => {
|
||||
const myCtx = ctx as RouterContext;
|
||||
|
||||
vscode.commands.executeCommand('mongoClusters.internal.documentView.open', {
|
||||
vscode.commands.executeCommand('command.internal.mongoClusters.documentView.open', {
|
||||
sessionId: myCtx.sessionId,
|
||||
databaseName: myCtx.databaseName,
|
||||
collectionName: myCtx.collectionName,
|
||||
|
@ -127,7 +127,7 @@ export const collectionsViewRouter = router({
|
|||
.mutation(({ input, ctx }) => {
|
||||
const myCtx = ctx as RouterContext;
|
||||
|
||||
vscode.commands.executeCommand('mongoClusters.internal.documentView.open', {
|
||||
vscode.commands.executeCommand('command.internal.mongoClusters.documentView.open', {
|
||||
sessionId: myCtx.sessionId,
|
||||
databaseName: myCtx.databaseName,
|
||||
collectionName: myCtx.collectionName,
|
||||
|
@ -142,7 +142,7 @@ export const collectionsViewRouter = router({
|
|||
.mutation(({ input, ctx }) => {
|
||||
const myCtx = ctx as RouterContext;
|
||||
|
||||
vscode.commands.executeCommand('mongoClusters.internal.documentView.open', {
|
||||
vscode.commands.executeCommand('command.internal.mongoClusters.documentView.open', {
|
||||
sessionId: myCtx.sessionId,
|
||||
databaseName: myCtx.databaseName,
|
||||
collectionName: myCtx.collectionName,
|
||||
|
@ -187,7 +187,7 @@ export const collectionsViewRouter = router({
|
|||
const myCtx = ctx as RouterContext;
|
||||
|
||||
vscode.commands.executeCommand(
|
||||
'mongoClusters.internal.exportDocuments',
|
||||
'command.internal.mongoClusters.exportDocuments',
|
||||
myCtx.collectionTreeItem,
|
||||
input.query,
|
||||
);
|
||||
|
@ -195,6 +195,6 @@ export const collectionsViewRouter = router({
|
|||
importDocuments: publicProcedure.query(async ({ ctx }) => {
|
||||
const myCtx = ctx as RouterContext;
|
||||
|
||||
vscode.commands.executeCommand('mongoClusters.internal.importDocuments', myCtx.collectionTreeItem);
|
||||
vscode.commands.executeCommand('command.internal.mongoClusters.importDocuments', myCtx.collectionTreeItem);
|
||||
}),
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче