From 32817ae52e3826318afa9e53f052c908245c31fc Mon Sep 17 00:00:00 2001 From: Cory Rivera Date: Wed, 6 Sep 2023 14:12:07 -0700 Subject: [PATCH] Handle undefined nodeInfo for top-level database objects (#24298) --- .../mssql/src/objectManagement/commands.ts | 110 ++++++++++-------- .../objectManagement/localizedConstants.ts | 1 + 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/extensions/mssql/src/objectManagement/commands.ts b/extensions/mssql/src/objectManagement/commands.ts index 5056add9c6..9bcfbe862e 100644 --- a/extensions/mssql/src/objectManagement/commands.ts +++ b/extensions/mssql/src/objectManagement/commands.ts @@ -69,40 +69,52 @@ async function handleNewObjectDialogCommand(context: azdata.ObjectExplorerContex if (!connectionUri) { return; } + let objectType: ObjectManagement.NodeType; - switch (context.nodeInfo!.objectType) { - case FolderType.ApplicationRoles: - objectType = ObjectManagement.NodeType.ApplicationRole; - break; - case FolderType.DatabaseRoles: - objectType = ObjectManagement.NodeType.DatabaseRole; - break; - case FolderType.ServerLevelLogins: - objectType = ObjectManagement.NodeType.ServerLevelLogin; - break; - case FolderType.ServerLevelServerRoles: - objectType = ObjectManagement.NodeType.ServerLevelServerRole; - break; - case FolderType.Users: - objectType = ObjectManagement.NodeType.User; - break; - case FolderType.Databases: - objectType = ObjectManagement.NodeType.Database; - break; - } - // Fall back to node type in case the user right clicked on an object instead of a folder - if (!objectType) { - switch (context.nodeInfo!.nodeType) { - case ObjectManagement.NodeType.ApplicationRole: - case ObjectManagement.NodeType.DatabaseRole: - case ObjectManagement.NodeType.ServerLevelLogin: - case ObjectManagement.NodeType.ServerLevelServerRole: - case ObjectManagement.NodeType.User: - case ObjectManagement.NodeType.Database: - objectType = context.nodeInfo!.nodeType as ObjectManagement.NodeType; + if (context.nodeInfo) { + switch (context.nodeInfo.objectType) { + case FolderType.ApplicationRoles: + objectType = ObjectManagement.NodeType.ApplicationRole; break; - default: - throw new Error(objectManagementLoc.NoDialogFoundError(context.nodeInfo!.nodeType, context.nodeInfo!.objectType)); + case FolderType.DatabaseRoles: + objectType = ObjectManagement.NodeType.DatabaseRole; + break; + case FolderType.ServerLevelLogins: + objectType = ObjectManagement.NodeType.ServerLevelLogin; + break; + case FolderType.ServerLevelServerRoles: + objectType = ObjectManagement.NodeType.ServerLevelServerRole; + break; + case FolderType.Users: + objectType = ObjectManagement.NodeType.User; + break; + case FolderType.Databases: + objectType = ObjectManagement.NodeType.Database; + break; + } + + // Fall back to node type in case the user right clicked on an object instead of a folder + if (!objectType) { + switch (context.nodeInfo.nodeType) { + case ObjectManagement.NodeType.ApplicationRole: + case ObjectManagement.NodeType.DatabaseRole: + case ObjectManagement.NodeType.ServerLevelLogin: + case ObjectManagement.NodeType.ServerLevelServerRole: + case ObjectManagement.NodeType.User: + case ObjectManagement.NodeType.Database: + objectType = context.nodeInfo.nodeType as ObjectManagement.NodeType; + break; + default: + throw new Error(objectManagementLoc.NoDialogFoundError(context.nodeInfo.nodeType, context.nodeInfo.objectType)); + } + } + } 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 we don't have a database name, then we have to assume it's a server node, which isn't valid for the New Object command. + if (context.connectionProfile?.databaseName?.length > 0) { + objectType = ObjectManagement.NodeType.Database; + } else { + throw new Error(objectManagementLoc.NotSupportedError(ObjectManagement.NodeType.Server)); } } @@ -111,7 +123,7 @@ async function handleNewObjectDialogCommand(context: azdata.ObjectExplorerContex const options: ObjectManagementDialogOptions = { connectionUri: connectionUri, isNewObject: true, - database: context.connectionProfile!.databaseName!, + database: context.connectionProfile?.databaseName, objectType: objectType, objectName: '', parentUrn: parentUrn, @@ -122,7 +134,7 @@ async function handleNewObjectDialogCommand(context: azdata.ObjectExplorerContex } catch (err) { TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenNewObjectDialog, err).withAdditionalProperties({ - objectType: context.nodeInfo!.nodeType + objectType: objectType }).send(); console.error(err); await vscode.window.showErrorMessage(objectManagementLoc.OpenNewObjectDialogError(objectManagementLoc.getNodeTypeDisplayName(objectType), getErrorMessage(err))); @@ -329,14 +341,16 @@ async function handleDropDatabase(context: azdata.ObjectExplorerContext, service } 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)}']`; const options: ObjectManagementDialogOptions = { connectionUri: connectionUri, isNewObject: false, database: context.connectionProfile!.databaseName!, - objectType: context.nodeInfo.nodeType as ObjectManagement.NodeType, - objectName: context.nodeInfo.label, + objectType: ObjectManagement.NodeType.Database, + objectName: objectName, parentUrn: parentUrn, - objectUrn: context.nodeInfo!.metadata!.urn, + objectUrn: objectUrn, objectExplorerContext: context }; const dialog = new DropDatabaseDialog(service, options); @@ -344,7 +358,7 @@ async function handleDropDatabase(context: azdata.ObjectExplorerContext, service } catch (err) { TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenDropDatabaseDialog, err).withAdditionalProperties({ - objectType: context.nodeInfo!.nodeType + objectType: ObjectManagement.NodeType.Database }).send(); console.error(err); await vscode.window.showErrorMessage(objectManagementLoc.OpenDropDatabaseDialogError(getErrorMessage(err))); @@ -380,12 +394,16 @@ async function getConnectionUri(context: azdata.ObjectExplorerContext): Promise< return connectionUri; } -async function getParentUrn(context: azdata.ObjectExplorerContext): Promise { - let node = undefined; - let currentNodePath = context.nodeInfo!.parentNodePath; - do { - node = await azdata.objectexplorer.getNode(context.connectionProfile!.id, currentNodePath); - currentNodePath = node?.parentNodePath; - } while (node && currentNodePath && !node.metadata?.urn); - return node?.metadata?.urn; +async function getParentUrn(context: azdata.ObjectExplorerContext): Promise { + let parentUrn: string = undefined; + if (context.nodeInfo) { + let node = undefined; + let currentNodePath = context.nodeInfo.parentNodePath; + do { + node = await azdata.objectexplorer.getNode(context.connectionProfile!.id, currentNodePath); + currentNodePath = node?.parentNodePath; + } while (node && currentNodePath && !node.metadata?.urn); + parentUrn = node?.metadata?.urn; + } + return parentUrn; } diff --git a/extensions/mssql/src/objectManagement/localizedConstants.ts b/extensions/mssql/src/objectManagement/localizedConstants.ts index f9d6d26e45..3505c7c54f 100644 --- a/extensions/mssql/src/objectManagement/localizedConstants.ts +++ b/extensions/mssql/src/objectManagement/localizedConstants.ts @@ -27,6 +27,7 @@ export const DatabaseRoleTypeDisplayName: string = localize('objectManagement.Da export const DatabaseRoleTypeDisplayNameInTitle: string = localize('objectManagement.DatabaseRoleTypeDisplayNameInTitle', "Database Role"); export const DatabaseTypeDisplayNameInTitle: string = localize('objectManagement.DatabaseDisplayNameInTitle', "Database"); export function NoDialogFoundError(nodeType: string, objectType: string): string { return localize('objectManagement.noDialogFoundError', "Could not find a supported dialog for node type '{0}' and object type '{1}'.", nodeType, objectType); } +export function NotSupportedError(objectType: string): string { return localize('objectManagement.notSupportedError', "This command is not supported for object type '{0}'.", objectType); } // Shared Strings export const FailedToRetrieveConnectionInfoErrorMessage: string = localize('objectManagement.noConnectionUriError', "Failed to retrieve the connection information, please reconnect and try again.")