From decbe8ddedeba1f770e78c289b7b2ea91c78020e Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Wed, 19 Apr 2023 19:26:29 -0700 Subject: [PATCH] simplify object management feature APIs (#22781) --- extensions/mssql/config.json | 2 +- extensions/mssql/package.json | 33 +- extensions/mssql/package.nls.json | 6 +- extensions/mssql/src/contracts.ts | 92 ++--- extensions/mssql/src/mssql.d.ts | 94 ++--- .../mssql/src/objectManagement/commands.ts | 118 +++--- .../mssql/src/objectManagement/constants.ts | 49 +-- .../objectManagement/localizedConstants.ts | 1 + .../objectManagementService.ts | 346 ++++++++---------- .../src/objectManagement/ui/loginDialog.ts | 51 +-- .../ui/objectManagementDialogBase.ts | 102 +++--- .../src/objectManagement/ui/userDialog.ts | 48 +-- .../mssql/src/objectManagement/utils.ts | 46 +-- .../objectExplorer/common/treeNode.ts | 3 +- 14 files changed, 413 insertions(+), 578 deletions(-) diff --git a/extensions/mssql/config.json b/extensions/mssql/config.json index c441ae618a..a8bf0279a1 100644 --- a/extensions/mssql/config.json +++ b/extensions/mssql/config.json @@ -1,6 +1,6 @@ { "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", - "version": "4.7.0.12", + "version": "4.7.0.14", "downloadFileNames": { "Windows_86": "win-x86-net7.0.zip", "Windows_64": "win-x64-net7.0.zip", diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index e0dc9de8ff..3101268bbe 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -73,14 +73,9 @@ "title": "%title.changeNotebookConnection%" }, { - "command": "mssql.newLogin", + "command": "mssql.newObject", "category": "MSSQL", - "title": "%title.newLogin%" - }, - { - "command": "mssql.newUser", - "category": "MSSQL", - "title": "%title.newUser%" + "title": "%title.newObject%" }, { "command": "mssql.objectProperties", @@ -469,11 +464,7 @@ "when": "false" }, { - "command": "mssql.newLogin", - "when": "false" - }, - { - "command": "mssql.newUser", + "command": "mssql.newObject", "when": "false" }, { @@ -502,13 +493,8 @@ "group": "0_query@1" }, { - "command": "mssql.newLogin", - "when": "connectionProvider == MSSQL && nodeType == Folder && objectType == ServerLevelLogins && config.workbench.enablePreviewFeatures", - "group": "0_query@1" - }, - { - "command": "mssql.newUser", - "when": "connectionProvider == MSSQL && nodeType == Folder && objectType == Users && config.workbench.enablePreviewFeatures", + "command": "mssql.newObject", + "when": "connectionProvider == MSSQL && nodeType == Folder && objectType =~ /^(ServerLevelLogins|Users)$/ && config.workbench.enablePreviewFeatures", "group": "0_query@1" }, { @@ -553,13 +539,8 @@ "group": "connection@1" }, { - "command": "mssql.newLogin", - "when": "connectionProvider == MSSQL && nodeType == Folder && objectType == ServerLevelLogins && config.workbench.enablePreviewFeatures", - "group": "connection@1" - }, - { - "command": "mssql.newUser", - "when": "connectionProvider == MSSQL && nodeType == Folder && objectType == Users && config.workbench.enablePreviewFeatures", + "command": "mssql.newObject", + "when": "connectionProvider == MSSQL && nodeType == Folder && objectType =~ /^(ServerLevelLogins|Users)$/ && config.workbench.enablePreviewFeatures", "group": "connection@1" }, { diff --git a/extensions/mssql/package.nls.json b/extensions/mssql/package.nls.json index 9b351a033c..fc825d4077 100644 --- a/extensions/mssql/package.nls.json +++ b/extensions/mssql/package.nls.json @@ -183,11 +183,7 @@ "mssql.objectExplorer.enableGroupBySchemaTitle": "SQL Server: Enable Group By Schema", "mssql.objectExplorer.disableGroupBySchemaTitle": "SQL Server: Disable Group By Schema", "mssql.objectExplorer.expandTimeout": "The timeout in seconds for expanding a node in Object Explorer. The default value is 45 seconds.", - "title.newServerRole": "New Server Role", - "title.newLogin": "New Login", - "title.newDatabaseRole": "New Database Role", - "title.newApplicationRole": "New Application Role", - "title.newUser": "New User", + "title.newObject": "New", "title.objectProperties": "Properties (Preview)", "title.deleteObject": "Delete", "title.renameObject": "Rename" diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts index a6a7122d2c..6f3a582d31 100644 --- a/extensions/mssql/src/contracts.ts +++ b/extensions/mssql/src/contracts.ts @@ -1534,103 +1534,51 @@ export namespace ExecutionPlanComparisonRequest { // ------------------------------- < Execution Plan > ------------------------------------ // ------------------------------- < Object Management > ------------------------------------ -export interface InitializeLoginViewRequestParams { +export interface InitializeViewRequestParams { connectionUri: string; - contextId: string; - isNewObject: boolean; - name: string | undefined; -} - -export namespace InitializeLoginViewRequest { - export const type = new RequestType('objectManagement/initializeLoginView'); -} - -export interface CreateLoginRequestParams { - contextId: string; - login: mssql.ObjectManagement.Login; -} - -export namespace CreateLoginRequest { - export const type = new RequestType('objectManagement/createLogin'); -} - -export interface ScriptLoginRequestParams { - contextId: string; - login: mssql.ObjectManagement.Login; -} - -export namespace ScriptLoginRequest { - export const type = new RequestType('objectManagement/scriptLogin'); -} - -export interface UpdateLoginRequestParams { - contextId: string; - login: mssql.ObjectManagement.Login; -} - -export namespace UpdateLoginRequest { - export const type = new RequestType('objectManagement/updateLogin'); -} - -export interface DisposeLoginViewRequestParams { - contextId: string; -} - -export namespace DisposeLoginViewRequest { - export const type = new RequestType('objectManagement/disposeLoginView'); -} - -export interface InitializeUserViewRequestParams { - connectionUri: string; - contextId: string; - isNewObject: boolean; database: string; - name: string | undefined; -} - -export namespace InitializeUserViewRequest { - export const type = new RequestType('objectManagement/initializeUserView'); -} - -export interface CreateUserRequestParams { contextId: string; - user: mssql.ObjectManagement.User; + isNewObject: boolean; + objectType: string; + parentUrn: string; + objectUrn?: string; } -export namespace CreateUserRequest { - export const type = new RequestType('objectManagement/createUser'); +export namespace InitializeViewRequest { + export const type = new RequestType, void, void>('objectManagement/initializeView'); } -export interface ScriptUserRequestParams { +export interface SaveObjectRequestParams { contextId: string; - user: mssql.ObjectManagement.User; + object: mssql.ObjectManagement.SqlObject; } -export namespace ScriptUserRequest { - export const type = new RequestType('objectManagement/scriptUser'); +export namespace SaveObjectRequest { + export const type = new RequestType('objectManagement/save'); } -export interface UpdateUserRequestParams { +export interface ScriptObjectRequestParams { contextId: string; - user: mssql.ObjectManagement.User; + object: mssql.ObjectManagement.SqlObject; } -export namespace UpdateUserRequest { - export const type = new RequestType('objectManagement/updateUser'); +export namespace ScriptObjectRequest { + export const type = new RequestType('objectManagement/script'); } -export interface DisposeUserViewRequestParams { +export interface DisposeViewRequestParams { contextId: string; } -export namespace DisposeUserViewRequest { - export const type = new RequestType('objectManagement/disposeUserView'); +export namespace DisposeViewRequest { + export const type = new RequestType('objectManagement/disposeView'); } export interface RenameObjectRequestParams { connectionUri: string; newName: string; objectUrn: string; + objectType: mssql.ObjectManagement.NodeType; } export namespace RenameObjectRequest { @@ -1640,11 +1588,13 @@ export namespace RenameObjectRequest { export interface DropObjectRequestParams { connectionUri: string; objectUrn: string; + objectType: mssql.ObjectManagement.NodeType; } export namespace DropObjectRequest { export const type = new RequestType('objectManagement/drop'); } + // ------------------------------- < Object Management > ------------------------------------ // ------------------------------- < Encryption IV/KEY updation Event > ------------------------------------ diff --git a/extensions/mssql/src/mssql.d.ts b/extensions/mssql/src/mssql.d.ts index 9e3da7fe98..e15e71a344 100644 --- a/extensions/mssql/src/mssql.d.ts +++ b/extensions/mssql/src/mssql.d.ts @@ -875,6 +875,19 @@ declare module 'mssql' { // Object Management - Begin. export namespace ObjectManagement { + + /** + * Object types. + */ + export const enum NodeType { + Column = "Column", + Database = "Database", + ServerLevelLogin = "ServerLevelLogin", + Table = "Table", + User = "User", + View = "View" + } + /** * Base interface for all the objects. */ @@ -963,7 +976,7 @@ declare module 'mssql' { /** * The authentication types. */ - export enum AuthenticationType { + export const enum AuthenticationType { Windows = 'Windows', Sql = 'Sql', AzureActiveDirectory = 'AAD' @@ -1086,7 +1099,7 @@ declare module 'mssql' { /** * User types. */ - export enum UserType { + export const enum UserType { /** * User with a server level login. */ @@ -1188,81 +1201,48 @@ declare module 'mssql' { export interface IObjectManagementService { /** - * Initialize the login view and return the information to render the view. + * Initialize the object view and return the information to render the view. + * @param contextId The context id of the view, generated by the extension and will be used in subsequent save/script/dispose operations. + * @param objectType The object type. * @param connectionUri The original connection's URI. - * @param contextId The context id of the view, generated by the extension and will be used in subsequent create/update/dispose operations. - * @param isNewObject Whether the view is for creating a new login object. - * @param name Name of the login. Only applicable when isNewObject is false. + * @param database The target database. + * @param isNewObject Whether the view is for creating a new object. + * @param parentUrn The parent object's URN. + * @param objectUrn The object's URN. */ - initializeLoginView(connectionUri: string, contextId: string, isNewObject: boolean, name: string | undefined): Thenable; + initializeView(contextId: string, objectType: ObjectManagement.NodeType, connectionUri: string, database: string, isNewObject: boolean, parentUrn: string, objectUrn: string): Thenable>; /** - * Create a login. - * @param contextId The login view's context id. - * @param login The login information. + * Save an object. + * @param contextId The object view's context id. + * @param object The object to be saved. */ - createLogin(contextId: string, login: ObjectManagement.Login): Thenable; + save(contextId: string, object: ObjectManagement.SqlObject): Thenable; /** - * Update a login. - * @param contextId The login view's context id. - * @param login The login information. + * Script an object. + * @param contextId The object view's context id. + * @param object The object to be scripted. */ - updateLogin(contextId: string, login: ObjectManagement.Login): Thenable; + script(contextId: string, object: ObjectManagement.SqlObject): Thenable; /** - * Script a login. - * @param contextId The login view's context id. - * @param login The login information. - */ - scriptLogin(contextId: string, login: ObjectManagement.Login): Thenable; - /** - * Dispose the login view. + * Dispose a view. * @param contextId The id of the view. */ - disposeLoginView(contextId: string): Thenable; - /** - * Initialize the user view and return the information to render the view. - * @param connectionUri The original connection's URI. - * @param database Name of the database. - * @param contextId The id of the view, generated by the extension and will be used in subsequent create/update/dispose operations. - * @param isNewObject Whether the view is for creating a new user object. - * @param name Name of the user. Only applicable when isNewObject is false. - */ - initializeUserView(connectionUri: string, database: string, contextId: string, isNewObject: boolean, name: string | undefined): Thenable; - /** - * Create a user. - * @param contextId Id of the view. - * @param user The user information. - */ - createUser(contextId: string, user: ObjectManagement.User): Thenable; - /** - * Update a user. - * @param contextId Id of the view. - * @param user The user information. - */ - updateUser(contextId: string, user: ObjectManagement.User): Thenable; - /** - * Script a user. - * @param contextId Id of the view. - * @param user The user information. - */ - scriptUser(contextId: string, user: ObjectManagement.User): Thenable; - /** - * Dispose the user view. - * @param contextId The id of the view. - */ - disposeUserView(contextId: string): Thenable; + disposeView(contextId: string): Thenable; /** * Rename an object. * @param connectionUri The URI of the server connection. + * @param objectType The object type. * @param objectUrn SMO Urn of the object to be renamed. More information: https://learn.microsoft.com/sql/relational-databases/server-management-objects-smo/overview-smo * @param newName The new name of the object. */ - rename(connectionUri: string, objectUrn: string, newName: string): Thenable; + rename(connectionUri: string, objectType: ObjectManagement.NodeType, objectUrn: string, newName: string): Thenable; /** * Drop an object. * @param connectionUri The URI of the server connection. + * @param objectType The object type. * @param objectUrn SMO Urn of the object to be dropped. More information: https://learn.microsoft.com/sql/relational-databases/server-management-objects-smo/overview-smo */ - drop(connectionUri: string, objectUrn: string): Thenable; + drop(connectionUri: string, objectType: ObjectManagement.NodeType, objectUrn: string): Thenable; } // Object Management - End. } diff --git a/extensions/mssql/src/objectManagement/commands.ts b/extensions/mssql/src/objectManagement/commands.ts index 701968eff6..aaedd48eee 100644 --- a/extensions/mssql/src/objectManagement/commands.ts +++ b/extensions/mssql/src/objectManagement/commands.ts @@ -9,22 +9,20 @@ import * as vscode from 'vscode'; import { LoginDialog } from './ui/loginDialog'; import { TestObjectManagementService } from './objectManagementService'; import { getErrorMessage } from '../utils'; -import { NodeType, TelemetryActions, TelemetryViews } from './constants'; +import { FolderType, TelemetryActions, ObjectManagementViewName } from './constants'; import * as localizedConstants from './localizedConstants'; import { UserDialog } from './ui/userDialog'; -import { IObjectManagementService } from 'mssql'; +import { IObjectManagementService, ObjectManagement } from 'mssql'; import * as constants from '../constants'; import { getNodeTypeDisplayName, refreshParentNode } from './utils'; import { TelemetryReporter } from '../telemetry'; +import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './ui/objectManagementDialogBase'; export function registerObjectManagementCommands(appContext: AppContext) { // Notes: Change the second parameter to false to use the actual object management service. const service = getObjectManagementService(appContext, false); - appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.newLogin', async (context: azdata.ObjectExplorerContext) => { - await handleNewLoginDialogCommand(context, service); - })); - appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.newUser', async (context: azdata.ObjectExplorerContext) => { - await handleNewUserDialogCommand(context, service); + appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.newObject', async (context: azdata.ObjectExplorerContext) => { + await handleNewObjectDialogCommand(context, service); })); appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.objectProperties', async (context: azdata.ObjectExplorerContext) => { await handleObjectPropertiesDialogCommand(context, service); @@ -45,41 +43,45 @@ function getObjectManagementService(appContext: AppContext, useTestService: bool } } -async function handleNewLoginDialogCommand(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise { +async function handleNewObjectDialogCommand(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise { const connectionUri = await getConnectionUri(context); if (!connectionUri) { return; } + let newObjectType: ObjectManagement.NodeType; + switch (context.nodeInfo!.objectType) { + case FolderType.ServerLevelLogins: + newObjectType = ObjectManagement.NodeType.ServerLevelLogin; + break; + case FolderType.Users: + newObjectType = ObjectManagement.NodeType.User; + break; + default: + throw new Error(`Unsupported folder type: ${context.nodeInfo!.objectType}`); + } try { - const dialog = new LoginDialog(service, connectionUri, true, undefined, context); + const parentUrn = await getParentUrn(context); + const options: ObjectManagementDialogOptions = { + connectionUri: connectionUri, + isNewObject: true, + database: context.connectionProfile!.databaseName!, + objectType: newObjectType, + objectName: '', + parentUrn: parentUrn, + objectExplorerContext: context + }; + const dialog = getDialog(service, options); await dialog.open(); } catch (err) { - TelemetryReporter.createErrorEvent2(TelemetryViews.ObjectManagement, TelemetryActions.OpenNewObjectDialog, err).withAdditionalProperties({ - objectType: NodeType.Login + TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenNewObjectDialog, err).withAdditionalProperties({ + objectType: context.nodeInfo!.nodeType }).send(); await vscode.window.showErrorMessage(localizedConstants.OpenNewObjectDialogError(localizedConstants.LoginTypeDisplayName, getErrorMessage(err))); } } -async function handleNewUserDialogCommand(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise { - const connectionUri = await getConnectionUri(context); - if (!connectionUri) { - return; - } - try { - const dialog = new UserDialog(service, connectionUri, context.connectionProfile!.databaseName!, true, undefined, context); - await dialog.open(); - } - catch (err) { - TelemetryReporter.createErrorEvent2(TelemetryViews.ObjectManagement, TelemetryActions.OpenNewObjectDialog, err).withAdditionalProperties({ - objectType: NodeType.User - }).send(); - await vscode.window.showErrorMessage(localizedConstants.OpenNewObjectDialogError(localizedConstants.UserTypeDisplayName, getErrorMessage(err))); - } -} - async function handleObjectPropertiesDialogCommand(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise { const connectionUri = await getConnectionUri(context); if (!connectionUri) { @@ -87,23 +89,22 @@ async function handleObjectPropertiesDialogCommand(context: azdata.ObjectExplore } const nodeTypeDisplayName = getNodeTypeDisplayName(context.nodeInfo!.nodeType); try { - let dialog; - switch (context.nodeInfo!.nodeType) { - case NodeType.Login: - dialog = new LoginDialog(service, connectionUri, false, context.nodeInfo!.label); - break; - case NodeType.User: - dialog = new UserDialog(service, connectionUri, context.connectionProfile!.databaseName!, false, context.nodeInfo!.label); - break; - default: - break; - } - if (dialog) { - await dialog.open(); - } + const parentUrn = await getParentUrn(context); + 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, + objectExplorerContext: context + }; + const dialog = getDialog(service, options); + await dialog.open(); } catch (err) { - TelemetryReporter.createErrorEvent2(TelemetryViews.ObjectManagement, TelemetryActions.OpenPropertiesDialog, err).withAdditionalProperties({ + TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenPropertiesDialog, err).withAdditionalProperties({ objectType: context.nodeInfo!.nodeType }).send(); await vscode.window.showErrorMessage(localizedConstants.OpenObjectPropertiesDialogError(nodeTypeDisplayName, context.nodeInfo!.label, getErrorMessage(err))); @@ -117,7 +118,7 @@ async function handleDeleteObjectCommand(context: azdata.ObjectExplorerContext, } let additionalConfirmationMessage: string | undefined = undefined; switch (context.nodeInfo!.nodeType) { - case NodeType.Login: + case ObjectManagement.NodeType.ServerLevelLogin: additionalConfirmationMessage = localizedConstants.DeleteLoginConfirmationText; break; default: @@ -139,7 +140,7 @@ async function handleDeleteObjectCommand(context: azdata.ObjectExplorerContext, operation: async (operation) => { try { const startTime = Date.now(); - await service.drop(connectionUri, context.nodeInfo!.metadata!.urn); + await service.drop(connectionUri, context.nodeInfo.nodeType as ObjectManagement.NodeType, context.nodeInfo!.metadata!.urn); TelemetryReporter.sendTelemetryEvent(TelemetryActions.DeleteObject, { objectType: context.nodeInfo!.nodeType }, { @@ -148,7 +149,7 @@ async function handleDeleteObjectCommand(context: azdata.ObjectExplorerContext, } catch (err) { operation.updateStatus(azdata.TaskStatus.Failed, localizedConstants.DeleteObjectError(nodeTypeDisplayName, context.nodeInfo!.label, getErrorMessage(err))); - TelemetryReporter.createErrorEvent2(TelemetryViews.ObjectManagement, TelemetryActions.DeleteObject, err).withAdditionalProperties({ + TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.DeleteObject, err).withAdditionalProperties({ objectType: context.nodeInfo!.nodeType }).send(); return; @@ -191,7 +192,7 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, operation: async (operation) => { try { const startTime = Date.now(); - await service.rename(connectionUri, context.nodeInfo!.metadata!.urn, newName); + await service.rename(connectionUri, context.nodeInfo.nodeType as ObjectManagement.NodeType, context.nodeInfo!.metadata!.urn, newName); TelemetryReporter.sendTelemetryEvent(TelemetryActions.RenameObject, { objectType: context.nodeInfo!.nodeType }, { @@ -200,7 +201,7 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, } catch (err) { operation.updateStatus(azdata.TaskStatus.Failed, localizedConstants.RenameObjectError(nodeTypeDisplayName, originalName, newName, getErrorMessage(err))); - TelemetryReporter.createErrorEvent2(TelemetryViews.ObjectManagement, TelemetryActions.RenameObject, err).withAdditionalProperties({ + TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.RenameObject, err).withAdditionalProperties({ objectType: context.nodeInfo!.nodeType }).send(); return; @@ -211,6 +212,17 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, }); } +function getDialog(service: IObjectManagementService, dialogOptions: ObjectManagementDialogOptions): ObjectManagementDialogBase> { + switch (dialogOptions.objectType) { + case ObjectManagement.NodeType.ServerLevelLogin: + return new LoginDialog(service, dialogOptions); + case ObjectManagement.NodeType.User: + return new UserDialog(service, dialogOptions); + default: + throw new Error(`Unsupported object type: ${dialogOptions.objectType}`); + } +} + async function getConnectionUri(context: azdata.ObjectExplorerContext): Promise { const connectionUri = await azdata.connection.getUriForConnection(context.connectionProfile!.id); if (!connectionUri) { @@ -218,3 +230,13 @@ 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; +} diff --git a/extensions/mssql/src/objectManagement/constants.ts b/extensions/mssql/src/objectManagement/constants.ts index 9da417d509..e41c2037ee 100644 --- a/extensions/mssql/src/objectManagement/constants.ts +++ b/extensions/mssql/src/objectManagement/constants.ts @@ -4,56 +4,21 @@ *--------------------------------------------------------------------------------------------*/ /** - * The object types in object explorer's node context. + * The folder types in object explorer. */ -export enum NodeType { - Column = 'Column', - Database = 'Database', - Login = 'ServerLevelLogin', - Table = 'Table', - User = 'User', - View = 'View' +export const enum FolderType { + ServerLevelLogins = 'ServerLevelLogins', + Users = 'Users' } export const PublicServerRoleName = 'public'; -/** - * User types. - */ -export enum UserType { - /** - * User with a server level login. - */ - WithLogin = 'WithLogin', - /** - * User based on a Windows user/group that has no login, but can connect to the Database Engine through membership in a Windows group. - */ - WithWindowsGroupLogin = 'WithWindowsGroupLogin', - /** - * Contained user, authentication is done within the database. - */ - Contained = 'Contained', - /** - * User that cannot authenticate. - */ - NoConnectAccess = 'NoConnectAccess' -} - -/** - * The authentication types. - */ -export enum AuthenticationType { - Windows = 'Windows', - Sql = 'Sql', - AzureActiveDirectory = 'AAD' -} - export const CreateUserDocUrl = 'https://learn.microsoft.com/en-us/sql/t-sql/statements/create-user-transact-sql'; export const AlterUserDocUrl = 'https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-user-transact-sql'; export const CreateLoginDocUrl = 'https://learn.microsoft.com/en-us/sql/t-sql/statements/create-login-transact-sql'; export const AlterLoginDocUrl = 'https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-login-transact-sql'; -export enum TelemetryActions { +export const enum TelemetryActions { CreateObject = 'CreateObject', DeleteObject = 'DeleteObject', OpenNewObjectDialog = 'OpenNewObjectDialog', @@ -62,6 +27,4 @@ export enum TelemetryActions { UpdateObject = 'UpdateObject' } -export enum TelemetryViews { - ObjectManagement = 'ObjectManagement' -} +export const ObjectManagementViewName = 'ObjectManagement'; diff --git a/extensions/mssql/src/objectManagement/localizedConstants.ts b/extensions/mssql/src/objectManagement/localizedConstants.ts index e5859e1dcc..4916d09fdc 100644 --- a/extensions/mssql/src/objectManagement/localizedConstants.ts +++ b/extensions/mssql/src/objectManagement/localizedConstants.ts @@ -24,6 +24,7 @@ export const LoadingDialogText: string = localize('objectManagement.loadingDialo export const FailedToRetrieveConnectionInfoErrorMessage: string = localize('objectManagement.noConnectionUriError', "Failed to retrieve the connection information, please reconnect and try again.") export const RenameObjectDialogTitle: string = localize('objectManagement.renameObjectDialogTitle', "Enter new name"); export const ScriptText: string = localize('objectManagement.scriptText', "Script"); +export const NoActionScriptedMessage: string = localize('objectManagement.noActionScriptedMessage', "There is no action to be scripted."); export const ScriptGeneratedText: string = localize('objectManagement.scriptGenerated', "Script has been generated successfully. You can close the dialog to view it in the newly opened editor.") diff --git a/extensions/mssql/src/objectManagement/objectManagementService.ts b/extensions/mssql/src/objectManagement/objectManagementService.ts index 58538faf37..1415b1fa48 100644 --- a/extensions/mssql/src/objectManagement/objectManagementService.ts +++ b/extensions/mssql/src/objectManagement/objectManagementService.ts @@ -11,7 +11,6 @@ import { BaseService, ISqlOpsFeature, SqlOpsDataClient } from 'dataprotocol-clie import { ObjectManagement, IObjectManagementService } from 'mssql'; import { ClientCapabilities } from 'vscode-languageclient'; import { AppContext } from '../appContext'; -import { AuthenticationType, UserType } from './constants'; export class ObjectManagementService extends BaseService implements IObjectManagementService { public static asFeature(context: AppContext): ISqlOpsFeature { @@ -29,235 +28,190 @@ export class ObjectManagementService extends BaseService implements IObjectManag }; } + async initializeView(contextId: string, objectType: ObjectManagement.NodeType, connectionUri: string, database: string, isNewObject: boolean, parentUrn: string, objectUrn: string): Promise> { + const params: contracts.InitializeViewRequestParams = { connectionUri, contextId, isNewObject, objectType, database, parentUrn, objectUrn }; + return this.runWithErrorHandling(contracts.InitializeViewRequest.type, params); + } + private constructor(context: AppContext, client: SqlOpsDataClient) { super(client); context.registerService(constants.ObjectManagementService, this); } - async initializeLoginView(connectionUri: string, contextId: string, isNewObject: boolean, name: string | undefined): Promise { - const params: contracts.InitializeLoginViewRequestParams = { connectionUri, contextId, isNewObject, name }; - return this.runWithErrorHandling(contracts.InitializeLoginViewRequest.type, params); + async save(contextId: string, object: ObjectManagement.SqlObject): Promise { + const params: contracts.SaveObjectRequestParams = { contextId, object }; + return this.runWithErrorHandling(contracts.SaveObjectRequest.type, params); } - async createLogin(contextId: string, login: ObjectManagement.Login): Promise { - const params: contracts.CreateLoginRequestParams = { contextId, login }; - return this.runWithErrorHandling(contracts.CreateLoginRequest.type, params); + async script(contextId: string, object: ObjectManagement.SqlObject): Promise { + const params: contracts.ScriptObjectRequestParams = { contextId, object }; + return this.runWithErrorHandling(contracts.ScriptObjectRequest.type, params); } - async updateLogin(contextId: string, login: ObjectManagement.Login): Promise { - const params: contracts.UpdateLoginRequestParams = { contextId, login }; - return this.runWithErrorHandling(contracts.UpdateLoginRequest.type, params); + async disposeView(contextId: string): Promise { + const params: contracts.DisposeViewRequestParams = { contextId }; + return this.runWithErrorHandling(contracts.DisposeViewRequest.type, params); } - async scriptLogin(contextId: string, login: ObjectManagement.Login): Promise { - const params: contracts.ScriptLoginRequestParams = { contextId, login }; - return this.runWithErrorHandling(contracts.ScriptLoginRequest.type, params); - } - - async disposeLoginView(contextId: string): Promise { - const params: contracts.DisposeLoginViewRequestParams = { contextId }; - return this.runWithErrorHandling(contracts.DisposeLoginViewRequest.type, params); - } - - async initializeUserView(connectionUri: string, database: string, contextId: string, isNewObject: boolean, name: string | undefined): Promise { - const params: contracts.InitializeUserViewRequestParams = { connectionUri, database, contextId, isNewObject, name }; - return this.runWithErrorHandling(contracts.InitializeUserViewRequest.type, params); - } - - async createUser(contextId: string, user: ObjectManagement.User): Promise { - const params: contracts.CreateUserRequestParams = { contextId, user }; - return this.runWithErrorHandling(contracts.CreateUserRequest.type, params); - } - - async updateUser(contextId: string, user: ObjectManagement.User): Promise { - const params: contracts.UpdateUserRequestParams = { contextId, user }; - return this.runWithErrorHandling(contracts.UpdateUserRequest.type, params); - } - - async scriptUser(contextId: string, user: ObjectManagement.User): Promise { - const params: contracts.ScriptUserRequestParams = { contextId, user }; - return this.runWithErrorHandling(contracts.ScriptUserRequest.type, params); - } - - async disposeUserView(contextId: string): Promise { - const params: contracts.DisposeUserViewRequestParams = { contextId }; - return this.runWithErrorHandling(contracts.DisposeUserViewRequest.type, params); - } - - async rename(connectionUri: string, objectUrn: string, newName: string): Promise { - const params: contracts.RenameObjectRequestParams = { connectionUri, objectUrn, newName }; + async rename(connectionUri: string, objectType: ObjectManagement.NodeType, objectUrn: string, newName: string): Promise { + const params: contracts.RenameObjectRequestParams = { connectionUri, objectUrn, newName, objectType }; return this.runWithErrorHandling(contracts.RenameObjectRequest.type, params); } - async drop(connectionUri: string, objectUrn: string): Promise { - const params: contracts.DropObjectRequestParams = { connectionUri, objectUrn }; + async drop(connectionUri: string, objectType: ObjectManagement.NodeType, objectUrn: string): Promise { + const params: contracts.DropObjectRequestParams = { connectionUri, objectUrn, objectType }; return this.runWithErrorHandling(contracts.DropObjectRequest.type, params); } } export class TestObjectManagementService implements IObjectManagementService { - initializeLoginView(connectionUri: string, contextId: string, isNewObject: boolean, name: string | undefined): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => { - const serverRoles = ['sysadmin', 'public', 'bulkadmin', 'dbcreator', 'diskadmin', 'processadmin', 'securityadmin', 'serveradmin']; - const languages = ['', 'English']; - const databases = ['master', 'db1', 'db2']; - let login: ObjectManagement.LoginViewInfo; - if (isNewObject) { - login = { - objectInfo: { - name: '', - authenticationType: AuthenticationType.Sql, - enforcePasswordPolicy: true, - enforcePasswordExpiration: true, - mustChangePassword: true, - defaultDatabase: 'master', - defaultLanguage: '', - serverRoles: ['public', 'bulkadmin'], - connectPermission: true, - isEnabled: true, - isLockedOut: false - }, - supportAADAuthentication: true, - supportSQLAuthentication: true, - supportWindowsAuthentication: true, - supportAdvancedOptions: true, - supportAdvancedPasswordOptions: true, - canEditLockedOutState: false, - languages: languages, - databases: databases, - serverRoles: serverRoles - }; - } else { - login = { - objectInfo: { - name: name, - authenticationType: AuthenticationType.Sql, - enforcePasswordPolicy: true, - enforcePasswordExpiration: true, - mustChangePassword: true, - defaultDatabase: 'master', - defaultLanguage: '', - serverRoles: ['public'], - connectPermission: true, - isEnabled: true, - isLockedOut: false, - password: '******************' - }, - supportAADAuthentication: true, - supportSQLAuthentication: true, - supportWindowsAuthentication: true, - supportAdvancedOptions: true, - supportAdvancedPasswordOptions: true, - canEditLockedOutState: false, - languages: languages, - databases: databases, - serverRoles: serverRoles - }; - } - resolve(login); - }, 3000); - }); + initializeView(contextId: string, objectType: ObjectManagement.NodeType, connectionUri: string, database: string, isNewObject: boolean, parentUrn: string, objectUrn: string): Thenable> { + if (objectType === ObjectManagement.NodeType.ServerLevelLogin) { + return Promise.resolve(this.getLoginView(isNewObject, objectUrn)); + } else if (objectType === ObjectManagement.NodeType.User) { + return Promise.resolve(this.getUserView(isNewObject, objectUrn)); + } + else { + throw Error('Not implemented'); + } } - async createLogin(contextId: string, login: ObjectManagement.Login): Promise { + save(contextId: string, object: ObjectManagement.SqlObject): Thenable { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, 3000); }); } - async updateLogin(contextId: string, login: ObjectManagement.Login): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve(); - }, 3000); - }); - } - async scriptLogin(contextId: string, login: ObjectManagement.Login): Promise { + script(contextId: string, object: ObjectManagement.SqlObject): Thenable { return new Promise((resolve, reject) => { setTimeout(() => { resolve('test script'); }, 1000); }); } - async disposeLoginView(contextId: string): Promise { - } - async initializeUserView(connectionUri: string, database: string, contextId: string, isNewObject: boolean, name: string): Promise { + disposeView(contextId: string): Thenable { return new Promise((resolve, reject) => { setTimeout(() => { - let viewInfo: ObjectManagement.UserViewInfo; - const languages = ['', 'English']; - const schemas = ['dbo', 'sys', 'alanren']; - const logins = ['sa', 'alanren', 'alanren@microsoft.com']; - const databaseRoles = ['dbmanager', 'loginmanager', 'bulkadmin', 'sysadmin', 'tablemanager', 'viewmanager']; + resolve(); + }, 100); + }); + } + async rename(connectionUri: string, objectType: ObjectManagement.NodeType, objectUrn: string, newName: string): Promise { + return this.delayAndResolve(); + } + async drop(connectionUri: string, objectType: ObjectManagement.NodeType, objectUrn: string): Promise { + return this.delayAndResolve(); + } + private getLoginView(isNewObject: boolean, name: string): ObjectManagement.LoginViewInfo { + const serverRoles = ['sysadmin', 'public', 'bulkadmin', 'dbcreator', 'diskadmin', 'processadmin', 'securityadmin', 'serveradmin']; + const languages = ['', 'English']; + const databases = ['master', 'db1', 'db2']; + let login: ObjectManagement.LoginViewInfo; + if (isNewObject) { + login = { + objectInfo: { + name: '', + authenticationType: ObjectManagement.AuthenticationType.Sql, + enforcePasswordPolicy: true, + enforcePasswordExpiration: true, + mustChangePassword: true, + defaultDatabase: 'master', + defaultLanguage: '', + serverRoles: ['public', 'bulkadmin'], + connectPermission: true, + isEnabled: true, + isLockedOut: false + }, + supportAADAuthentication: true, + supportSQLAuthentication: true, + supportWindowsAuthentication: true, + supportAdvancedOptions: true, + supportAdvancedPasswordOptions: true, + canEditLockedOutState: false, + languages: languages, + databases: databases, + serverRoles: serverRoles + }; + } else { + login = { + objectInfo: { + name: name, + authenticationType: ObjectManagement.AuthenticationType.Sql, + enforcePasswordPolicy: true, + enforcePasswordExpiration: true, + mustChangePassword: true, + defaultDatabase: 'master', + defaultLanguage: '', + serverRoles: ['public'], + connectPermission: true, + isEnabled: true, + isLockedOut: false, + password: '******************' + }, + supportAADAuthentication: true, + supportSQLAuthentication: true, + supportWindowsAuthentication: true, + supportAdvancedOptions: true, + supportAdvancedPasswordOptions: true, + canEditLockedOutState: false, + languages: languages, + databases: databases, + serverRoles: serverRoles + }; + } + return login; + } + private getUserView(isNewObject: boolean, name: string): ObjectManagement.UserViewInfo { + let viewInfo: ObjectManagement.UserViewInfo; + const languages = ['', 'English']; + const schemas = ['dbo', 'sys', 'alanren']; + const logins = ['sa', 'alanren', 'alanren@microsoft.com']; + const databaseRoles = ['dbmanager', 'loginmanager', 'bulkadmin', 'sysadmin', 'tablemanager', 'viewmanager']; - if (isNewObject) { - viewInfo = { - objectInfo: { - name: '', - type: UserType.WithLogin, - defaultSchema: 'dbo', - defaultLanguage: '', - authenticationType: AuthenticationType.Sql, - loginName: 'sa', - ownedSchemas: [], - databaseRoles: [], - password: '' - }, - languages: languages, - schemas: schemas, - logins: logins, - databaseRoles: databaseRoles, - supportContainedUser: true, - supportAADAuthentication: true, - supportSQLAuthentication: true, - supportWindowsAuthentication: true - }; - } else { - viewInfo = { - objectInfo: { - name: name, - type: UserType.WithLogin, - defaultSchema: 'dbo', - defaultLanguage: '', - loginName: 'sa', - authenticationType: AuthenticationType.Sql, - ownedSchemas: ['dbo'], - databaseRoles: ['dbmanager', 'bulkadmin'] - }, - languages: languages, - schemas: schemas, - logins: logins, - databaseRoles: databaseRoles, - supportContainedUser: true, - supportAADAuthentication: true, - supportSQLAuthentication: true, - supportWindowsAuthentication: true - }; - } - resolve(viewInfo); - }, 3000); - }); - } - async createUser(contextId: string, user: ObjectManagement.User): Promise { - return this.delayAndResolve(); - } - async updateUser(contextId: string, login: ObjectManagement.User): Promise { - return this.delayAndResolve(); - } - async scriptUser(contextId: string, login: ObjectManagement.User): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => { - reject('generate script for user not supported'); - }, 1000); - }); - } - async disposeUserView(contextId: string): Promise { - } - async rename(connectionUri: string, objectUrn: string, newName: string): Promise { - return this.delayAndResolve(); - } - async drop(connectionUri: string, objectUrn: string): Promise { - return this.delayAndResolve(); + if (isNewObject) { + viewInfo = { + objectInfo: { + name: '', + type: ObjectManagement.UserType.WithLogin, + defaultSchema: 'dbo', + defaultLanguage: '', + authenticationType: ObjectManagement.AuthenticationType.Sql, + loginName: 'sa', + ownedSchemas: [], + databaseRoles: [], + password: '' + }, + languages: languages, + schemas: schemas, + logins: logins, + databaseRoles: databaseRoles, + supportContainedUser: true, + supportAADAuthentication: true, + supportSQLAuthentication: true, + supportWindowsAuthentication: true + }; + } else { + viewInfo = { + objectInfo: { + name: name, + type: ObjectManagement.UserType.WithLogin, + defaultSchema: 'dbo', + defaultLanguage: '', + loginName: 'sa', + authenticationType: ObjectManagement.AuthenticationType.Sql, + ownedSchemas: ['dbo'], + databaseRoles: ['dbmanager', 'bulkadmin'] + }, + languages: languages, + schemas: schemas, + logins: logins, + databaseRoles: databaseRoles, + supportContainedUser: true, + supportAADAuthentication: true, + supportSQLAuthentication: true, + supportWindowsAuthentication: true + }; + } + return viewInfo; } private delayAndResolve(): Promise { return new Promise((resolve, reject) => { diff --git a/extensions/mssql/src/objectManagement/ui/loginDialog.ts b/extensions/mssql/src/objectManagement/ui/loginDialog.ts index 7119d866e0..0fbefc3443 100644 --- a/extensions/mssql/src/objectManagement/ui/loginDialog.ts +++ b/extensions/mssql/src/objectManagement/ui/loginDialog.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { DefaultInputWidth, ObjectManagementDialogBase } from './objectManagementDialogBase'; +import { DefaultInputWidth, ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; import { IObjectManagementService, ObjectManagement } from 'mssql'; import * as localizedConstants from '../localizedConstants'; -import { AlterLoginDocUrl, AuthenticationType, CreateLoginDocUrl, NodeType, PublicServerRoleName } from '../constants'; +import { AlterLoginDocUrl, CreateLoginDocUrl, PublicServerRoleName } from '../constants'; import { getAuthenticationTypeByDisplayName, getAuthenticationTypeDisplayName, isValidSQLPassword } from '../utils'; export class LoginDialog extends ObjectManagementDialogBase { - private generalSection: azdata.GroupContainer; private sqlAuthSection: azdata.GroupContainer; private serverRoleSection: azdata.GroupContainer; @@ -32,15 +31,19 @@ export class LoginDialog extends ObjectManagementDialogBase { // Empty password is only allowed when advanced password options are supported and the password policy check is off. // To match the SSMS behavior, a warning is shown to the user. if (this.viewInfo.supportAdvancedPasswordOptions - && this.objectInfo.authenticationType === AuthenticationType.Sql + && this.objectInfo.authenticationType === ObjectManagement.AuthenticationType.Sql && !this.objectInfo.password && !this.objectInfo.enforcePasswordPolicy) { const result = await vscode.window.showWarningMessage(localizedConstants.BlankPasswordConfirmationText, { modal: true }, localizedConstants.YesText); @@ -54,14 +57,14 @@ export class LoginDialog extends ObjectManagementDialogBase { - if (this.isNewObject) { - await this.objectManagementService.createLogin(this.contextId, this.objectInfo); - } else { - await this.objectManagementService.updateLogin(this.contextId, this.objectInfo); - } - } - - protected async disposeView(): Promise { - await this.objectManagementService.disposeLoginView(this.contextId); - } - - protected async initializeData(): Promise { - const viewInfo = await this.objectManagementService.initializeLoginView(this.connectionUri, this.contextId, this.isNewObject, this.objectName); - viewInfo.objectInfo.password = viewInfo.objectInfo.password ?? ''; - return viewInfo; + protected override postInitializeData(): void { + this.objectInfo.password = this.objectInfo.password ?? ''; } protected async initializeUI(): Promise { @@ -99,7 +88,7 @@ export class LoginDialog extends ObjectManagementDialogBase { - return this.objectManagementService.scriptLogin(this.contextId, this.objectInfo); - } - private initializeGeneralSection(): void { this.nameInput = this.modelView.modelBuilder.inputBox().withProps({ ariaLabel: localizedConstants.NameText, - enabled: this.isNewObject, + enabled: this.options.isNewObject, value: this.objectInfo.name, width: DefaultInputWidth }).component(); @@ -142,7 +127,7 @@ export class LoginDialog extends ObjectManagementDialogBase { this.objectInfo.authenticationType = getAuthenticationTypeByDisplayName(this.authTypeDropdown.value); this.setViewByAuthenticationType(); @@ -175,7 +160,7 @@ export class LoginDialog extends ObjectManagementDialogBase { diff --git a/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts b/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts index 1c3c3ed01d..817e607132 100644 --- a/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts +++ b/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts @@ -12,11 +12,11 @@ import * as vscode from 'vscode'; import { EOL } from 'os'; import { generateUuid } from 'vscode-languageclient/lib/utils/uuid'; import { getErrorMessage } from '../../utils'; -import { NodeType, TelemetryActions, TelemetryViews } from '../constants'; +import { TelemetryActions, ObjectManagementViewName } from '../constants'; import { CreateObjectOperationDisplayName, HelpText, LoadingDialogText, NameText, - NewObjectDialogTitle, ObjectPropertiesDialogTitle, OkText, ScriptError, ScriptGeneratedText, ScriptText, SelectedText, UpdateObjectOperationDisplayName + NewObjectDialogTitle, NoActionScriptedMessage, ObjectPropertiesDialogTitle, OkText, ScriptError, ScriptGeneratedText, ScriptText, SelectedText, UpdateObjectOperationDisplayName } from '../localizedConstants'; import { deepClone, getNodeTypeDisplayName, refreshNode } from '../utils'; import { TelemetryReporter } from '../../telemetry'; @@ -34,14 +34,26 @@ export function getTableHeight(rowCount: number, minRowCount: number = DefaultTa return Math.min(Math.max(rowCount, minRowCount) * TableRowHeight + TableColumnHeaderHeight, maxHeight); } -function getDialogName(type: NodeType, isNewObject: boolean): string { +function getDialogName(type: ObjectManagement.NodeType, isNewObject: boolean): string { return isNewObject ? `New${type}` : `${type}Properties` } +export interface ObjectManagementDialogOptions { + connectionUri: string; + database?: string; + objectType: ObjectManagement.NodeType; + isNewObject: boolean; + parentUrn: string; + objectUrn?: string; + objectExplorerContext?: azdata.ObjectExplorerContext; + width?: azdata.window.DialogWidth; + objectName?: string; +} + export abstract class ObjectManagementDialogBase> { protected readonly disposables: vscode.Disposable[] = []; protected readonly dialogObject: azdata.window.Dialog; - protected readonly contextId: string; + private _contextId: string; private _viewInfo: ViewInfoType; private _originalObjectInfo: ObjectInfoType; private _modelView: azdata.ModelView; @@ -50,28 +62,22 @@ export abstract class ObjectManagementDialogBase { await this.dispose(reason); })); this._helpButton = azdata.window.createButton(HelpText, 'left'); this.disposables.push(this._helpButton.onClick(async () => { - await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(docUrl)); + await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(this.docUrl)); })); this._scriptButton = azdata.window.createButton(ScriptText, 'left'); this.disposables.push(this._scriptButton.onClick(async () => { await this.onScriptButtonClick(); })); this.dialogObject.customButtons = [this._helpButton, this._scriptButton]; this.updateLoadingStatus(true); - this.contextId = generateUuid(); + this._contextId = generateUuid(); this.dialogObject.registerCloseValidator(async (): Promise => { const confirmed = await this.onConfirmation(); if (!confirmed) { @@ -81,18 +87,13 @@ export abstract class ObjectManagementDialogBase; protected abstract initializeUI(): Promise; - protected abstract onComplete(): Promise; - protected async onDispose(): Promise { } protected abstract validateInput(): Promise; - protected abstract generateScript(): Promise; + protected abstract get docUrl(): string; - /** - * Dispose the information related to this view in the backend service. - */ - protected abstract disposeView(): Promise; + protected postInitializeData(): void { + } protected onObjectValueChange(): void { this.dialogObject.okButton.enabled = this.isDirty; } @@ -141,28 +142,28 @@ export abstract class ObjectManagementDialogBase => { - const actionName = this.isNewObject ? TelemetryActions.CreateObject : TelemetryActions.UpdateObject; + const actionName = this.options.isNewObject ? TelemetryActions.CreateObject : TelemetryActions.UpdateObject; try { if (JSON.stringify(this.objectInfo) !== JSON.stringify(this._originalObjectInfo)) { const startTime = Date.now(); - await this.onComplete(); - if (this.isNewObject && this.objectExplorerContext) { - await refreshNode(this.objectExplorerContext); + await this.objectManagementService.save(this._contextId, this.objectInfo); + if (this.options.isNewObject && this.options.objectExplorerContext) { + await refreshNode(this.options.objectExplorerContext); } TelemetryReporter.sendTelemetryEvent(actionName, { - objectType: this.objectType + objectType: this.options.objectType }, { elapsedTimeMs: Date.now() - startTime }); @@ -171,8 +172,8 @@ export abstract class ObjectManagementDialogBase { - await this.onDispose(); this.disposables.forEach(disposable => disposable.dispose()); if (reason !== 'ok') { await this.disposeView(); } } + private async disposeView(): Promise { + await this.objectManagementService.disposeView(this._contextId); + } + + private async initializeData(): Promise { + const viewInfo = await this.objectManagementService.initializeView(this._contextId, this.options.objectType, this.options.connectionUri, this.options.database, this.options.isNewObject, this.options.parentUrn, this.options.objectUrn); + this._viewInfo = viewInfo as ViewInfoType; + this.postInitializeData(); + } + protected async runValidation(showErrorMessage: boolean = true): Promise { const errors = await this.validateInput(); if (errors.length > 0 && (this.dialogObject.message?.text || showErrorMessage)) { @@ -335,10 +345,16 @@ export abstract class ObjectManagementDialogBase { @@ -31,14 +31,16 @@ export class UserDialog extends ObjectManagementDialogBase { - const viewInfo = await this.objectManagementService.initializeUserView(this.connectionUri, this.database, this.contextId, this.isNewObject, this.objectName); - viewInfo.objectInfo.password = viewInfo.objectInfo.password ?? ''; - return viewInfo; + protected override get docUrl(): string { + return this.options.isNewObject ? CreateUserDocUrl : AlterUserDocUrl; + } + + protected override postInitializeData(): void { + this.objectInfo.password = this.objectInfo.password ?? ''; } protected async validateInput(): Promise { @@ -46,7 +48,7 @@ export class UserDialog extends ObjectManagementDialogBase { - if (this.isNewObject) { - await this.objectManagementService.createUser(this.contextId, this.objectInfo); - } else { - await this.objectManagementService.updateUser(this.contextId, this.objectInfo); - } - } - - protected async disposeView(): Promise { - await this.objectManagementService.disposeUserView(this.contextId); - } - protected async initializeUI(): Promise { this.initializeGeneralSection(); this.initializeOwnedSchemaSection(); @@ -88,14 +78,10 @@ export class UserDialog extends ObjectManagementDialogBase { - return this.objectManagementService.scriptUser(this.contextId, this.objectInfo); - } - private initializeGeneralSection(): void { this.nameInput = this.modelView.modelBuilder.inputBox().withProps({ ariaLabel: localizedConstants.NameText, - enabled: this.isNewObject, + enabled: this.options.isNewObject, value: this.objectInfo.name, width: DefaultInputWidth }).component(); @@ -114,7 +100,7 @@ export class UserDialog extends ObjectManagementDialogBase { this.objectInfo.type = getUserTypeByDisplayName(this.typeDropdown.value); this.onObjectValueChange(); @@ -122,7 +108,7 @@ export class UserDialog extends ObjectManagementDialogBase { this.objectInfo.loginName = this.loginDropdown.value; this.onObjectValueChange(); @@ -140,7 +126,7 @@ export class UserDialog extends ObjectManagementDialogBase { this.objectInfo.authenticationType = getAuthenticationTypeByDisplayName(this.authTypeDropdown.value); diff --git a/extensions/mssql/src/objectManagement/utils.ts b/extensions/mssql/src/objectManagement/utils.ts index 256ed9bc08..56fc9b4c08 100644 --- a/extensions/mssql/src/objectManagement/utils.ts +++ b/extensions/mssql/src/objectManagement/utils.ts @@ -6,7 +6,7 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { getErrorMessage } from '../utils'; -import { AuthenticationType, NodeType, UserType } from './constants'; +import { ObjectManagement } from 'mssql'; import { AADAuthenticationTypeDisplayText, ColumnTypeDisplayName, ContainedUserText, DatabaseTypeDisplayName, LoginTypeDisplayName, LoginTypeDisplayNameInTitle, RefreshObjectExplorerError, SQLAuthenticationTypeDisplayText, TableTypeDisplayName, UserTypeDisplayName, UserTypeDisplayNameInTitle, UserWithLoginText, UserWithNoConnectAccess, UserWithWindowsGroupLoginText, ViewTypeDisplayName, WindowsAuthenticationTypeDisplayText } from './localizedConstants'; export function deepClone(obj: T): T { @@ -55,70 +55,70 @@ export async function refreshNode(context: azdata.ObjectExplorerContext): Promis export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): string { switch (type) { - case NodeType.Login: + case ObjectManagement.NodeType.ServerLevelLogin: return inTitle ? LoginTypeDisplayNameInTitle : LoginTypeDisplayName; - case NodeType.User: + case ObjectManagement.NodeType.User: return inTitle ? UserTypeDisplayNameInTitle : UserTypeDisplayName; - case NodeType.Table: + case ObjectManagement.NodeType.Table: return TableTypeDisplayName; - case NodeType.View: + case ObjectManagement.NodeType.View: return ViewTypeDisplayName; - case NodeType.Column: + case ObjectManagement.NodeType.Column: return ColumnTypeDisplayName; - case NodeType.Database: + case ObjectManagement.NodeType.Database: return DatabaseTypeDisplayName; default: throw new Error(`Unkown node type: ${type}`); } } -export function getAuthenticationTypeDisplayName(authType: AuthenticationType | undefined): string | undefined { +export function getAuthenticationTypeDisplayName(authType: ObjectManagement.AuthenticationType | undefined): string | undefined { if (authType === undefined) { return undefined; } switch (authType) { - case AuthenticationType.Windows: + case ObjectManagement.AuthenticationType.Windows: return WindowsAuthenticationTypeDisplayText; - case AuthenticationType.AzureActiveDirectory: + case ObjectManagement.AuthenticationType.AzureActiveDirectory: return AADAuthenticationTypeDisplayText; default: return SQLAuthenticationTypeDisplayText; } } -export function getAuthenticationTypeByDisplayName(displayValue: string): AuthenticationType { +export function getAuthenticationTypeByDisplayName(displayValue: string): ObjectManagement.AuthenticationType { switch (displayValue) { case WindowsAuthenticationTypeDisplayText: - return AuthenticationType.Windows; + return ObjectManagement.AuthenticationType.Windows; case AADAuthenticationTypeDisplayText: - return AuthenticationType.AzureActiveDirectory; + return ObjectManagement.AuthenticationType.AzureActiveDirectory; default: - return AuthenticationType.Sql; + return ObjectManagement.AuthenticationType.Sql; } } -export function getUserTypeDisplayName(userType: UserType): string { +export function getUserTypeDisplayName(userType: ObjectManagement.UserType): string { switch (userType) { - case UserType.WithLogin: + case ObjectManagement.UserType.WithLogin: return UserWithLoginText; - case UserType.WithWindowsGroupLogin: + case ObjectManagement.UserType.WithWindowsGroupLogin: return UserWithWindowsGroupLoginText; - case UserType.Contained: + case ObjectManagement.UserType.Contained: return ContainedUserText; default: return UserWithNoConnectAccess; } } -export function getUserTypeByDisplayName(userTypeDisplayName: string): UserType { +export function getUserTypeByDisplayName(userTypeDisplayName: string): ObjectManagement.UserType { switch (userTypeDisplayName) { case UserWithLoginText: - return UserType.WithLogin; + return ObjectManagement.UserType.WithLogin; case UserWithWindowsGroupLoginText: - return UserType.WithWindowsGroupLogin; + return ObjectManagement.UserType.WithWindowsGroupLogin; case ContainedUserText: - return UserType.Contained; + return ObjectManagement.UserType.Contained; default: - return UserType.NoConnectAccess; + return ObjectManagement.UserType.NoConnectAccess; } } diff --git a/src/sql/workbench/services/objectExplorer/common/treeNode.ts b/src/sql/workbench/services/objectExplorer/common/treeNode.ts index fe5e8f915a..545a7883cc 100644 --- a/src/sql/workbench/services/objectExplorer/common/treeNode.ts +++ b/src/sql/workbench/services/objectExplorer/common/treeNode.ts @@ -171,7 +171,8 @@ export class TreeNode { label: this.label, isLeaf: this.isAlwaysLeaf, metadata: this.metadata, - errorMessage: this.errorStateMessage + errorMessage: this.errorStateMessage, + objectType: this.objectType }; }