diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 1c5f207c6e..3720a69dc3 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -578,7 +578,7 @@ }, { "command": "mssql.renameObject", - "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|Table|View|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && config.workbench.enablePreviewFeatures", + "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|Table|View|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && config.workbench.enablePreviewFeatures", "group": "1_objectManagement@1" }, { diff --git a/extensions/mssql/src/objectManagement/commands.ts b/extensions/mssql/src/objectManagement/commands.ts index 9bcfbe862e..5a765d7201 100644 --- a/extensions/mssql/src/objectManagement/commands.ts +++ b/extensions/mssql/src/objectManagement/commands.ts @@ -146,20 +146,16 @@ async function handleObjectPropertiesDialogCommand(context: azdata.ObjectExplore if (!connectionUri) { return; } + const object = await getObjectInfoForContext(context); try { - const parentUrn = context.isConnectionNode ? undefined : await getParentUrn(context); - const objectType = context.nodeInfo ? context.nodeInfo.nodeType as ObjectManagement.NodeType : (context.connectionProfile.databaseName === '' ? ObjectManagement.NodeType.Server : ObjectManagement.NodeType.Database); - const objectName = context.nodeInfo ? context.nodeInfo.label : (!context.connectionProfile.databaseName ? context.connectionProfile.serverName : context.connectionProfile.databaseName); - const objectUrn = context.nodeInfo ? context.nodeInfo!.metadata!.urn : (context.connectionProfile.databaseName === '' ? 'Server' : `Server/Database[@Name='${escapeSingleQuotes(context.connectionProfile.databaseName)}']`); - const options: ObjectManagementDialogOptions = { connectionUri: connectionUri, isNewObject: false, - database: context.connectionProfile!.databaseName!, - objectType: objectType, - objectName: objectName, - parentUrn: parentUrn, - objectUrn: objectUrn, + database: context.connectionProfile?.databaseName, + objectType: object.type, + objectName: object.name, + parentUrn: object.parentUrn, + objectUrn: object.urn, objectExplorerContext: context }; const dialog = getDialog(service, options); @@ -167,10 +163,10 @@ async function handleObjectPropertiesDialogCommand(context: azdata.ObjectExplore } catch (err) { TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenPropertiesDialog, err).withAdditionalProperties({ - objectType: context.nodeInfo!.nodeType + objectType: object.type }).send(); console.error(err); - await vscode.window.showErrorMessage(objectManagementLoc.OpenObjectPropertiesDialogError(objectManagementLoc.getNodeTypeDisplayName(context.nodeInfo!.nodeType), context.nodeInfo!.label, getErrorMessage(err))); + await vscode.window.showErrorMessage(objectManagementLoc.OpenObjectPropertiesDialogError(objectManagementLoc.getNodeTypeDisplayName(object.type), object.name, getErrorMessage(err))); } } @@ -179,16 +175,18 @@ async function handleDropObjectCommand(context: azdata.ObjectExplorerContext, se if (!connectionUri) { return; } + + const object = await getObjectInfoForContext(context); let additionalConfirmationMessage: string | undefined = undefined; - switch (context.nodeInfo!.nodeType) { + switch (object.type) { case ObjectManagement.NodeType.ServerLevelLogin: additionalConfirmationMessage = objectManagementLoc.DropLoginConfirmationText; break; default: break; } - const nodeTypeDisplayName = objectManagementLoc.getNodeTypeDisplayName(context.nodeInfo!.nodeType); - let confirmMessage = objectManagementLoc.DropObjectConfirmationText(nodeTypeDisplayName, context.nodeInfo!.label); + const nodeTypeDisplayName = objectManagementLoc.getNodeTypeDisplayName(object.type); + let confirmMessage = objectManagementLoc.DropObjectConfirmationText(nodeTypeDisplayName, object.name); if (additionalConfirmationMessage) { confirmMessage = `${additionalConfirmationMessage} ${confirmMessage}`; } @@ -197,23 +195,23 @@ async function handleDropObjectCommand(context: azdata.ObjectExplorerContext, se return; } azdata.tasks.startBackgroundOperation({ - displayName: objectManagementLoc.DropObjectOperationDisplayName(nodeTypeDisplayName, context.nodeInfo!.label), + displayName: objectManagementLoc.DropObjectOperationDisplayName(nodeTypeDisplayName, object.name), description: '', isCancelable: false, operation: async (operation) => { try { const startTime = Date.now(); - await service.drop(connectionUri, context.nodeInfo.nodeType as ObjectManagement.NodeType, context.nodeInfo!.metadata!.urn); + await service.drop(connectionUri, object.type, object.urn); TelemetryReporter.sendTelemetryEvent(TelemetryActions.DropObject, { - objectType: context.nodeInfo!.nodeType + objectType: object.type }, { elapsedTimeMs: Date.now() - startTime }); } catch (err) { - operation.updateStatus(azdata.TaskStatus.Failed, objectManagementLoc.DropObjectError(nodeTypeDisplayName, context.nodeInfo!.label, getErrorMessage(err))); + operation.updateStatus(azdata.TaskStatus.Failed, objectManagementLoc.DropObjectError(nodeTypeDisplayName, object.name, getErrorMessage(err))); TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.DropObject, err).withAdditionalProperties({ - objectType: context.nodeInfo!.nodeType + objectType: object.type }).send(); console.error(err); return; @@ -229,11 +227,12 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, if (!connectionUri) { return; } - const nodeTypeDisplayName = objectManagementLoc.getNodeTypeDisplayName(context.nodeInfo!.nodeType); - const originalName = context.nodeInfo!.metadata!.name; + + const object = await getObjectInfoForContext(context); + const nodeTypeDisplayName = objectManagementLoc.getNodeTypeDisplayName(object.type); const newName = await vscode.window.showInputBox({ title: objectManagementLoc.RenameObjectDialogTitle, - value: originalName, + value: object.name, validateInput: (value: string): string | undefined => { if (!value) { return objectManagementLoc.NameCannotBeEmptyError; @@ -245,28 +244,28 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, }); // return if no change was made or the dialog was canceled. - if (newName === originalName || !newName) { + if (newName === object.name || !newName) { return; } azdata.tasks.startBackgroundOperation({ - displayName: objectManagementLoc.RenameObjectOperationDisplayName(nodeTypeDisplayName, originalName, newName), + displayName: objectManagementLoc.RenameObjectOperationDisplayName(nodeTypeDisplayName, object.name, newName), description: '', isCancelable: false, operation: async (operation) => { try { const startTime = Date.now(); - await service.rename(connectionUri, context.nodeInfo.nodeType as ObjectManagement.NodeType, context.nodeInfo!.metadata!.urn, newName); + await service.rename(connectionUri, object.type, object.urn, newName); TelemetryReporter.sendTelemetryEvent(TelemetryActions.RenameObject, { - objectType: context.nodeInfo!.nodeType + objectType: object.type }, { elapsedTimeMs: Date.now() - startTime }); } catch (err) { - operation.updateStatus(azdata.TaskStatus.Failed, objectManagementLoc.RenameObjectError(nodeTypeDisplayName, originalName, newName, getErrorMessage(err))); + operation.updateStatus(azdata.TaskStatus.Failed, objectManagementLoc.RenameObjectError(nodeTypeDisplayName, object.name, newName, getErrorMessage(err))); TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.RenameObject, err).withAdditionalProperties({ - objectType: context.nodeInfo!.nodeType + objectType: object.type }).send(); console.error(err); return; @@ -282,16 +281,19 @@ async function handleDetachDatabase(context: azdata.ObjectExplorerContext, servi if (!connectionUri) { return; } + const object = await getObjectInfoForContext(context); try { - const parentUrn = await getParentUrn(context); + if (object.type !== ObjectManagement.NodeType.Database) { + throw new Error(objectManagementLoc.NotSupportedError(ObjectManagement.NodeType.Database)); + } const options: ObjectManagementDialogOptions = { connectionUri: connectionUri, isNewObject: false, - database: context.connectionProfile!.databaseName!, - objectType: context.nodeInfo.nodeType as ObjectManagement.NodeType, - objectName: context.nodeInfo.label, - parentUrn: parentUrn, - objectUrn: context.nodeInfo!.metadata!.urn, + database: object.name, + objectType: object.type, + objectName: object.name, + parentUrn: object.parentUrn, + objectUrn: object.urn, objectExplorerContext: context }; const dialog = new DetachDatabaseDialog(service, options); @@ -299,7 +301,7 @@ async function handleDetachDatabase(context: azdata.ObjectExplorerContext, servi } catch (err) { TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenDetachDatabaseDialog, err).withAdditionalProperties({ - objectType: context.nodeInfo!.nodeType + objectType: object.type }).send(); console.error(err); await vscode.window.showErrorMessage(objectManagementLoc.OpenDetachDatabaseDialogError(getErrorMessage(err))); @@ -339,18 +341,19 @@ async function handleDropDatabase(context: azdata.ObjectExplorerContext, service if (!connectionUri) { return; } + const object = await getObjectInfoForContext(context); try { - const parentUrn = await getParentUrn(context); - const objectName = context.nodeInfo?.label ?? context.connectionProfile.databaseName; - const objectUrn = context.nodeInfo?.metadata?.urn ?? `Server/Database[@Name='${escapeSingleQuotes(context.connectionProfile.databaseName)}']`; + if (object.type !== ObjectManagement.NodeType.Database) { + throw new Error(objectManagementLoc.NotSupportedError(ObjectManagement.NodeType.Database)); + } const options: ObjectManagementDialogOptions = { connectionUri: connectionUri, isNewObject: false, - database: context.connectionProfile!.databaseName!, - objectType: ObjectManagement.NodeType.Database, - objectName: objectName, - parentUrn: parentUrn, - objectUrn: objectUrn, + database: object.name, + objectType: object.type, + objectName: object.name, + parentUrn: object.parentUrn, + objectUrn: object.urn, objectExplorerContext: context }; const dialog = new DropDatabaseDialog(service, options); @@ -358,7 +361,7 @@ async function handleDropDatabase(context: azdata.ObjectExplorerContext, service } catch (err) { TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenDropDatabaseDialog, err).withAdditionalProperties({ - objectType: ObjectManagement.NodeType.Database + objectType: object.type }).send(); console.error(err); await vscode.window.showErrorMessage(objectManagementLoc.OpenDropDatabaseDialogError(getErrorMessage(err))); @@ -407,3 +410,39 @@ async function getParentUrn(context: azdata.ObjectExplorerContext): Promise { + let nodeType: ObjectManagement.NodeType; + let objectName: string; + let objectUrn: string; + if (context.nodeInfo) { + nodeType = context.nodeInfo.nodeType as ObjectManagement.NodeType; + objectName = context.nodeInfo.metadata?.name; + objectUrn = context.nodeInfo.metadata?.urn; + } else { + // Node info will be missing for top level connection items like servers and databases, so make a best guess here based on connection info. + if (context.connectionProfile?.databaseName?.length > 0) { + nodeType = ObjectManagement.NodeType.Database; + objectName = context.connectionProfile.databaseName; + objectUrn = `Server/Database[@Name='${escapeSingleQuotes(objectName)}']`; + } else { + nodeType = ObjectManagement.NodeType.Server; + objectName = context.connectionProfile.serverName; + objectUrn = 'Server'; + } + } + let parentUrn = await getParentUrn(context); + return { + parentUrn: parentUrn, + name: objectName, + type: nodeType, + urn: objectUrn + } +}