diff --git a/extensions/mssql/src/objectManagement/commands.ts b/extensions/mssql/src/objectManagement/commands.ts index 431ac43b92..6825492de3 100644 --- a/extensions/mssql/src/objectManagement/commands.ts +++ b/extensions/mssql/src/objectManagement/commands.ts @@ -10,7 +10,8 @@ import { LoginDialog } from './ui/loginDialog'; import { TestObjectManagementService } from './objectManagementService'; import { getErrorMessage } from '../utils'; import { FolderType, TelemetryActions, ObjectManagementViewName } from './constants'; -import * as localizedConstants from './localizedConstants'; +import * as objectManagementLoc from './localizedConstants'; +import * as uiLoc from '../ui/localizedConstants'; import { UserDialog } from './ui/userDialog'; import { IObjectManagementService, ObjectManagement } from 'mssql'; import * as constants from '../constants'; @@ -91,7 +92,7 @@ async function handleNewObjectDialogCommand(context: azdata.ObjectExplorerContex objectType: context.nodeInfo!.nodeType }).send(); console.error(err); - await vscode.window.showErrorMessage(localizedConstants.OpenNewObjectDialogError(localizedConstants.getNodeTypeDisplayName(objectType), getErrorMessage(err))); + await vscode.window.showErrorMessage(objectManagementLoc.OpenNewObjectDialogError(objectManagementLoc.getNodeTypeDisplayName(objectType), getErrorMessage(err))); } } @@ -120,7 +121,7 @@ async function handleObjectPropertiesDialogCommand(context: azdata.ObjectExplore objectType: context.nodeInfo!.nodeType }).send(); console.error(err); - await vscode.window.showErrorMessage(localizedConstants.OpenObjectPropertiesDialogError(localizedConstants.getNodeTypeDisplayName(context.nodeInfo!.nodeType), context.nodeInfo!.label, getErrorMessage(err))); + await vscode.window.showErrorMessage(objectManagementLoc.OpenObjectPropertiesDialogError(objectManagementLoc.getNodeTypeDisplayName(context.nodeInfo!.nodeType), context.nodeInfo!.label, getErrorMessage(err))); } } @@ -132,22 +133,22 @@ async function handleDeleteObjectCommand(context: azdata.ObjectExplorerContext, let additionalConfirmationMessage: string | undefined = undefined; switch (context.nodeInfo!.nodeType) { case ObjectManagement.NodeType.ServerLevelLogin: - additionalConfirmationMessage = localizedConstants.DeleteLoginConfirmationText; + additionalConfirmationMessage = objectManagementLoc.DeleteLoginConfirmationText; break; default: break; } - const nodeTypeDisplayName = localizedConstants.getNodeTypeDisplayName(context.nodeInfo!.nodeType); - let confirmMessage = localizedConstants.DeleteObjectConfirmationText(nodeTypeDisplayName, context.nodeInfo!.label); + const nodeTypeDisplayName = objectManagementLoc.getNodeTypeDisplayName(context.nodeInfo!.nodeType); + let confirmMessage = objectManagementLoc.DeleteObjectConfirmationText(nodeTypeDisplayName, context.nodeInfo!.label); if (additionalConfirmationMessage) { confirmMessage = `${additionalConfirmationMessage} ${confirmMessage}`; } - const confirmResult = await vscode.window.showWarningMessage(confirmMessage, { modal: true }, localizedConstants.YesText); - if (confirmResult !== localizedConstants.YesText) { + const confirmResult = await vscode.window.showWarningMessage(confirmMessage, { modal: true }, uiLoc.YesText); + if (confirmResult !== uiLoc.YesText) { return; } azdata.tasks.startBackgroundOperation({ - displayName: localizedConstants.DeleteObjectOperationDisplayName(nodeTypeDisplayName, context.nodeInfo!.label), + displayName: objectManagementLoc.DeleteObjectOperationDisplayName(nodeTypeDisplayName, context.nodeInfo!.label), description: '', isCancelable: false, operation: async (operation) => { @@ -161,7 +162,7 @@ async function handleDeleteObjectCommand(context: azdata.ObjectExplorerContext, }); } catch (err) { - operation.updateStatus(azdata.TaskStatus.Failed, localizedConstants.DeleteObjectError(nodeTypeDisplayName, context.nodeInfo!.label, getErrorMessage(err))); + operation.updateStatus(azdata.TaskStatus.Failed, objectManagementLoc.DeleteObjectError(nodeTypeDisplayName, context.nodeInfo!.label, getErrorMessage(err))); TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.DeleteObject, err).withAdditionalProperties({ objectType: context.nodeInfo!.nodeType }).send(); @@ -179,14 +180,14 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, if (!connectionUri) { return; } - const nodeTypeDisplayName = localizedConstants.getNodeTypeDisplayName(context.nodeInfo!.nodeType); + const nodeTypeDisplayName = objectManagementLoc.getNodeTypeDisplayName(context.nodeInfo!.nodeType); const originalName = context.nodeInfo!.metadata!.name; const newName = await vscode.window.showInputBox({ - title: localizedConstants.RenameObjectDialogTitle, + title: objectManagementLoc.RenameObjectDialogTitle, value: originalName, validateInput: (value: string): string | undefined => { if (!value) { - return localizedConstants.NameCannotBeEmptyError; + return objectManagementLoc.NameCannotBeEmptyError; } else { // valid return undefined; @@ -200,7 +201,7 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, } azdata.tasks.startBackgroundOperation({ - displayName: localizedConstants.RenameObjectOperationDisplayName(nodeTypeDisplayName, originalName, newName), + displayName: objectManagementLoc.RenameObjectOperationDisplayName(nodeTypeDisplayName, originalName, newName), description: '', isCancelable: false, operation: async (operation) => { @@ -214,7 +215,7 @@ async function handleRenameObjectCommand(context: azdata.ObjectExplorerContext, }); } catch (err) { - operation.updateStatus(azdata.TaskStatus.Failed, localizedConstants.RenameObjectError(nodeTypeDisplayName, originalName, newName, getErrorMessage(err))); + operation.updateStatus(azdata.TaskStatus.Failed, objectManagementLoc.RenameObjectError(nodeTypeDisplayName, originalName, newName, getErrorMessage(err))); TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.RenameObject, err).withAdditionalProperties({ objectType: context.nodeInfo!.nodeType }).send(); @@ -247,7 +248,7 @@ function getDialog(service: IObjectManagementService, dialogOptions: ObjectManag async function getConnectionUri(context: azdata.ObjectExplorerContext): Promise { const connectionUri = await azdata.connection.getUriForConnection(context.connectionProfile!.id); if (!connectionUri) { - await vscode.window.showErrorMessage(localizedConstants.FailedToRetrieveConnectionInfoErrorMessage, { modal: true }); + await vscode.window.showErrorMessage(objectManagementLoc.FailedToRetrieveConnectionInfoErrorMessage, { modal: true }); } return connectionUri; } diff --git a/extensions/mssql/src/objectManagement/localizedConstants.ts b/extensions/mssql/src/objectManagement/localizedConstants.ts index 23a5570dbf..745f561b06 100644 --- a/extensions/mssql/src/objectManagement/localizedConstants.ts +++ b/extensions/mssql/src/objectManagement/localizedConstants.ts @@ -24,20 +24,11 @@ export const DatabaseRoleTypeDisplayName: string = localize('objectManagement.Da export const DatabaseRoleTypeDisplayNameInTitle: string = localize('objectManagement.DatabaseRoleTypeDisplayNameInTitle', "Database Role"); // Shared Strings -export const HelpText: string = localize('objectManagement.helpText', "Help"); -export const YesText: string = localize('objectManagement.yesText', "Yes"); -export const OkText: string = localize('objectManagement.OkText', "OK"); -export const LoadingDialogText: string = localize('objectManagement.loadingDialog', "Loading dialog..."); 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.") export const OwnerText: string = localize('objectManagement.ownerText', "Owner"); export const BrowseText = localize('objectManagement.browseText', "Browse…"); export const BrowseOwnerButtonAriaLabel = localize('objectManagement.browseForOwnerText', "Browse for an owner"); -export const AddText = localize('objectManagement.addText', "Add…"); -export const RemoveText = localize('objectManagement.removeText', "Remove"); export const AddMemberAriaLabel = localize('objectManagement.addMemberText', "Add a member"); export const RemoveMemberAriaLabel = localize('objectManagement.removeMemberText', "Remove selected member"); @@ -126,12 +117,7 @@ export function RenameObjectError(objectType: string, originalName: string, newN }, "An error occurred while renaming {0} '{1}' to '{2}'. {3}", objectType, originalName, newName, error); } -export function ScriptError(error: string): string { - return localize('objectManagement.scriptError', "An error occurred while generating script. {0}", error); -} - export const NameText = localize('objectManagement.nameLabel', "Name"); -export const SelectedText = localize('objectManagement.selectedLabel', "Selected"); export const GeneralSectionHeader = localize('objectManagement.generalSectionHeader', "General"); export const AdvancedSectionHeader = localize('objectManagement.advancedSectionHeader', "Advanced"); export const PasswordText = localize('objectManagement.passwordLabel', "Password"); diff --git a/extensions/mssql/src/objectManagement/ui/applicationRoleDialog.ts b/extensions/mssql/src/objectManagement/ui/applicationRoleDialog.ts index a9b386454d..8504cd2efa 100644 --- a/extensions/mssql/src/objectManagement/ui/applicationRoleDialog.ts +++ b/extensions/mssql/src/objectManagement/ui/applicationRoleDialog.ts @@ -8,7 +8,7 @@ import { IObjectManagementService, ObjectManagement } from 'mssql'; import * as localizedConstants from '../localizedConstants'; import { AlterApplicationRoleDocUrl, CreateApplicationRoleDocUrl } from '../constants'; import { isValidSQLPassword } from '../utils'; -import { DefaultMaxTableHeight } from './dialogBase'; +import { DefaultMaxTableHeight } from '../../ui/dialogBase'; export class ApplicationRoleDialog extends ObjectManagementDialogBase { // Sections @@ -32,7 +32,7 @@ export class ApplicationRoleDialog extends ObjectManagementDialogBase { // Sections @@ -30,7 +30,7 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase { private generalSection: azdata.GroupContainer; @@ -36,7 +37,7 @@ export class LoginDialog extends ObjectManagementDialogBase { + this.nameInput = this.createInputBox(objectManagementLoc.NameText, async (newValue) => { this.objectInfo.name = newValue; }, this.objectInfo.name, this.options.isNewObject); - const nameContainer = this.createLabelInputContainer(localizedConstants.NameText, this.nameInput); - this.authTypeDropdown = this.createDropdown(localizedConstants.AuthTypeText, + const nameContainer = this.createLabelInputContainer(objectManagementLoc.NameText, this.nameInput); + this.authTypeDropdown = this.createDropdown(objectManagementLoc.AuthTypeText, async (newValue) => { - this.objectInfo.authenticationType = localizedConstants.getAuthenticationTypeByDisplayName(newValue); + this.objectInfo.authenticationType = objectManagementLoc.getAuthenticationTypeByDisplayName(newValue); this.setViewByAuthenticationType(); }, - this.viewInfo.authenticationTypes.map(authType => localizedConstants.getAuthenticationTypeDisplayName(authType)), - localizedConstants.getAuthenticationTypeDisplayName(this.objectInfo.authenticationType), + this.viewInfo.authenticationTypes.map(authType => objectManagementLoc.getAuthenticationTypeDisplayName(authType)), + objectManagementLoc.getAuthenticationTypeDisplayName(this.objectInfo.authenticationType), this.options.isNewObject); - const authTypeContainer = this.createLabelInputContainer(localizedConstants.AuthTypeText, this.authTypeDropdown); + const authTypeContainer = this.createLabelInputContainer(objectManagementLoc.AuthTypeText, this.authTypeDropdown); - this.enabledCheckbox = this.createCheckbox(localizedConstants.EnabledText, async (checked) => { + this.enabledCheckbox = this.createCheckbox(objectManagementLoc.EnabledText, async (checked) => { this.objectInfo.isEnabled = checked; }, this.objectInfo.isEnabled); - this.generalSection = this.createGroup(localizedConstants.GeneralSectionHeader, [nameContainer, authTypeContainer, this.enabledCheckbox], false); + this.generalSection = this.createGroup(objectManagementLoc.GeneralSectionHeader, [nameContainer, authTypeContainer, this.enabledCheckbox], false); } private initializeSqlAuthSection(): void { const items: azdata.Component[] = []; - this.passwordInput = this.createPasswordInputBox(localizedConstants.PasswordText, async (newValue) => { + this.passwordInput = this.createPasswordInputBox(objectManagementLoc.PasswordText, async (newValue) => { this.objectInfo.password = newValue; }, this.objectInfo.password ?? ''); - const passwordRow = this.createLabelInputContainer(localizedConstants.PasswordText, this.passwordInput); - this.confirmPasswordInput = this.createPasswordInputBox(localizedConstants.ConfirmPasswordText, async () => { }, this.objectInfo.password ?? ''); - const confirmPasswordRow = this.createLabelInputContainer(localizedConstants.ConfirmPasswordText, this.confirmPasswordInput); + const passwordRow = this.createLabelInputContainer(objectManagementLoc.PasswordText, this.passwordInput); + this.confirmPasswordInput = this.createPasswordInputBox(objectManagementLoc.ConfirmPasswordText, async () => { }, this.objectInfo.password ?? ''); + const confirmPasswordRow = this.createLabelInputContainer(objectManagementLoc.ConfirmPasswordText, this.confirmPasswordInput); items.push(passwordRow, confirmPasswordRow); if (!this.options.isNewObject) { - this.specifyOldPasswordCheckbox = this.createCheckbox(localizedConstants.SpecifyOldPasswordText, async (checked) => { + this.specifyOldPasswordCheckbox = this.createCheckbox(objectManagementLoc.SpecifyOldPasswordText, async (checked) => { this.oldPasswordInput.enabled = this.specifyOldPasswordCheckbox.checked; this.objectInfo.oldPassword = ''; if (!this.specifyOldPasswordCheckbox.checked) { this.oldPasswordInput.value = ''; } }); - this.oldPasswordInput = this.createPasswordInputBox(localizedConstants.OldPasswordText, async (newValue) => { + this.oldPasswordInput = this.createPasswordInputBox(objectManagementLoc.OldPasswordText, async (newValue) => { this.objectInfo.oldPassword = newValue; }, '', false); - const oldPasswordRow = this.createLabelInputContainer(localizedConstants.OldPasswordText, this.oldPasswordInput); + const oldPasswordRow = this.createLabelInputContainer(objectManagementLoc.OldPasswordText, this.oldPasswordInput); items.push(this.specifyOldPasswordCheckbox, oldPasswordRow); } if (this.viewInfo.supportAdvancedPasswordOptions) { - this.enforcePasswordPolicyCheckbox = this.createCheckbox(localizedConstants.EnforcePasswordPolicyText, async (checked) => { + this.enforcePasswordPolicyCheckbox = this.createCheckbox(objectManagementLoc.EnforcePasswordPolicyText, async (checked) => { const enforcePolicy = checked; this.objectInfo.enforcePasswordPolicy = enforcePolicy; this.enforcePasswordExpirationCheckbox.enabled = enforcePolicy; @@ -159,68 +160,68 @@ export class LoginDialog extends ObjectManagementDialogBase { + this.enforcePasswordExpirationCheckbox = this.createCheckbox(objectManagementLoc.EnforcePasswordExpirationText, async (checked) => { const enforceExpiration = checked; this.objectInfo.enforcePasswordExpiration = enforceExpiration; this.mustChangePasswordCheckbox.enabled = enforceExpiration; this.mustChangePasswordCheckbox.checked = enforceExpiration; }, this.objectInfo.enforcePasswordPolicy); - this.mustChangePasswordCheckbox = this.createCheckbox(localizedConstants.MustChangePasswordText, async (checked) => { + this.mustChangePasswordCheckbox = this.createCheckbox(objectManagementLoc.MustChangePasswordText, async (checked) => { this.objectInfo.mustChangePassword = checked; }, this.objectInfo.mustChangePassword); items.push(this.enforcePasswordPolicyCheckbox, this.enforcePasswordExpirationCheckbox, this.mustChangePasswordCheckbox); if (!this.options.isNewObject) { - this.lockedOutCheckbox = this.createCheckbox(localizedConstants.LoginLockedOutText, async (checked) => { + this.lockedOutCheckbox = this.createCheckbox(objectManagementLoc.LoginLockedOutText, async (checked) => { this.objectInfo.isLockedOut = checked; }, this.objectInfo.isLockedOut, this.viewInfo.canEditLockedOutState); items.push(this.lockedOutCheckbox); } } - this.sqlAuthSection = this.createGroup(localizedConstants.SQLAuthenticationSectionHeader, items); + this.sqlAuthSection = this.createGroup(objectManagementLoc.SQLAuthenticationSectionHeader, items); } private initializeAdvancedSection(): void { const items: azdata.Component[] = []; if (this.viewInfo.supportAdvancedOptions) { - this.defaultDatabaseDropdown = this.createDropdown(localizedConstants.DefaultDatabaseText, async (newValue) => { + this.defaultDatabaseDropdown = this.createDropdown(objectManagementLoc.DefaultDatabaseText, async (newValue) => { this.objectInfo.defaultDatabase = newValue; }, this.viewInfo.databases, this.objectInfo.defaultDatabase); - const defaultDatabaseContainer = this.createLabelInputContainer(localizedConstants.DefaultDatabaseText, this.defaultDatabaseDropdown); + const defaultDatabaseContainer = this.createLabelInputContainer(objectManagementLoc.DefaultDatabaseText, this.defaultDatabaseDropdown); - this.defaultLanguageDropdown = this.createDropdown(localizedConstants.DefaultLanguageText, async (newValue) => { + this.defaultLanguageDropdown = this.createDropdown(objectManagementLoc.DefaultLanguageText, async (newValue) => { this.objectInfo.defaultLanguage = newValue; }, this.viewInfo.languages, this.objectInfo.defaultLanguage); - const defaultLanguageContainer = this.createLabelInputContainer(localizedConstants.DefaultLanguageText, this.defaultLanguageDropdown); + const defaultLanguageContainer = this.createLabelInputContainer(objectManagementLoc.DefaultLanguageText, this.defaultLanguageDropdown); - this.connectPermissionCheckbox = this.createCheckbox(localizedConstants.PermissionToConnectText, async (checked) => { + this.connectPermissionCheckbox = this.createCheckbox(objectManagementLoc.PermissionToConnectText, async (checked) => { this.objectInfo.connectPermission = checked; }, this.objectInfo.connectPermission); items.push(defaultDatabaseContainer, defaultLanguageContainer, this.connectPermissionCheckbox); } - this.advancedSection = this.createGroup(localizedConstants.AdvancedSectionHeader, items); + this.advancedSection = this.createGroup(objectManagementLoc.AdvancedSectionHeader, items); } private initializeServerRolesSection(): void { - this.serverRoleTable = this.createTableList(localizedConstants.ServerRoleSectionHeader, - [localizedConstants.ServerRoleTypeDisplayNameInTitle], + this.serverRoleTable = this.createTableList(objectManagementLoc.ServerRoleSectionHeader, + [objectManagementLoc.ServerRoleTypeDisplayNameInTitle], this.viewInfo.serverRoles, this.objectInfo.serverRoles, DefaultMaxTableHeight, (item) => { return item !== PublicServerRoleName }); - this.serverRoleSection = this.createGroup(localizedConstants.ServerRoleSectionHeader, [this.serverRoleTable]); + this.serverRoleSection = this.createGroup(objectManagementLoc.ServerRoleSectionHeader, [this.serverRoleTable]); } private setViewByAuthenticationType(): void { - if (this.authTypeDropdown.value === localizedConstants.SQLAuthenticationTypeDisplayText) { + if (this.authTypeDropdown.value === objectManagementLoc.SQLAuthenticationTypeDisplayText) { this.addItem(this.formContainer, this.sqlAuthSection, 1); - } else if (this.authTypeDropdown.value !== localizedConstants.SQLAuthenticationTypeDisplayText) { + } else if (this.authTypeDropdown.value !== objectManagementLoc.SQLAuthenticationTypeDisplayText) { this.removeItem(this.formContainer, this.sqlAuthSection); } } diff --git a/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts b/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts index 199291a469..f78313faa6 100644 --- a/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts +++ b/extensions/mssql/src/objectManagement/ui/objectManagementDialogBase.ts @@ -5,23 +5,21 @@ import * as azdata from 'azdata'; import { IObjectManagementService, ObjectManagement } from 'mssql'; -import * as vscode from 'vscode'; import { generateUuid } from 'vscode-languageclient/lib/utils/uuid'; import * as localizedConstants from '../localizedConstants'; -import { deepClone, refreshNode, refreshParentNode } from '../utils'; -import { DialogBase } from './dialogBase'; +import { refreshNode, refreshParentNode } from '../utils'; import { ObjectManagementViewName, TelemetryActions } from '../constants'; import { TelemetryReporter } from '../../telemetry'; import { getErrorMessage } from '../../utils'; -import { providerId } from '../../constants'; -import { equals } from '../../util/objects'; +import { deepClone, equals } from '../../util/objects'; +import { ScriptableDialogBase, ScriptableDialogOptions } from '../../ui/scriptableDialogBase'; function getDialogName(type: ObjectManagement.NodeType, isNewObject: boolean): string { return isNewObject ? `New${type}` : `${type}Properties` } -export interface ObjectManagementDialogOptions { +export interface ObjectManagementDialogOptions extends ScriptableDialogOptions { connectionUri: string; database?: string; objectType: ObjectManagement.NodeType; @@ -29,44 +27,25 @@ export interface ObjectManagementDialogOptions { parentUrn: string; objectUrn?: string; objectExplorerContext?: azdata.ObjectExplorerContext; - width?: azdata.window.DialogWidth; objectName?: string; } -export abstract class ObjectManagementDialogBase> extends DialogBase { +export abstract class ObjectManagementDialogBase> extends ScriptableDialogBase { private _contextId: string; private _viewInfo: ViewInfoType; private _originalObjectInfo: ObjectInfoType; - private _helpButton: azdata.window.Button; - private _scriptButton: azdata.window.Button; - constructor(protected readonly objectManagementService: IObjectManagementService, protected readonly options: ObjectManagementDialogOptions) { + constructor(protected readonly objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { super(options.isNewObject ? localizedConstants.NewObjectDialogTitle(localizedConstants.getNodeTypeDisplayName(options.objectType, true)) : localizedConstants.ObjectPropertiesDialogTitle(localizedConstants.getNodeTypeDisplayName(options.objectType, true), options.objectName), getDialogName(options.objectType, options.isNewObject), - options.width || 'narrow', 'flyout' + options ); - this._helpButton = azdata.window.createButton(localizedConstants.HelpText, 'left'); - this.disposables.push(this._helpButton.onClick(async () => { - await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(this.docUrl)); - })); - this._scriptButton = azdata.window.createButton(localizedConstants.ScriptText, 'left'); - this.disposables.push(this._scriptButton.onClick(async () => { await this.onScriptButtonClick(); })); - this.dialogObject.customButtons = [this._helpButton, this._scriptButton]; this._contextId = generateUuid(); } - protected abstract initializeUI(): Promise; - - protected abstract get docUrl(): string; - protected postInitializeData(): void { } - protected override onFormFieldChange(): void { - this._scriptButton.enabled = this.isDirty; - this.dialogObject.okButton.enabled = this.isDirty; - } - protected override async validateInput(): Promise { const errors: string[] = []; if (!this.objectInfo.name) { @@ -76,8 +55,7 @@ export abstract class ObjectManagementDialogBase { - await this.initializeData(); - await this.initializeUI(); + await super.initialize(); const typeDisplayName = localizedConstants.getNodeTypeDisplayName(this.options.objectType); this.dialogObject.registerOperation({ displayName: this.options.isNewObject ? localizedConstants.CreateObjectOperationDisplayName(typeDisplayName) @@ -146,49 +124,18 @@ export abstract class ObjectManagementDialogBase { + protected override 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(); this._originalObjectInfo = deepClone(this.objectInfo); } - protected override onLoadingStatusChanged(isLoading: boolean): void { - super.onLoadingStatusChanged(isLoading); - this._helpButton.enabled = !isLoading; - this.dialogObject.okButton.enabled = this._scriptButton.enabled = isLoading ? false : this.isDirty; + protected override async generateScript(): Promise { + return await this.objectManagementService.script(this._contextId, this.objectInfo); } - private async onScriptButtonClick(): Promise { - this.onLoadingStatusChanged(true); - try { - const isValid = await this.runValidation(); - if (!isValid) { - return; - } - let message: string; - const script = await this.objectManagementService.script(this._contextId, this.objectInfo); - if (script) { - message = localizedConstants.ScriptGeneratedText; - await azdata.queryeditor.openQueryDocument({ content: script }, providerId); - } else { - message = localizedConstants.NoActionScriptedMessage; - } - this.dialogObject.message = { - text: message, - level: azdata.window.MessageLevel.Information - }; - } catch (err) { - this.dialogObject.message = { - text: localizedConstants.ScriptError(getErrorMessage(err)), - level: azdata.window.MessageLevel.Error - }; - } finally { - this.onLoadingStatusChanged(false); - } - } - - private get isDirty(): boolean { + protected get isDirty(): boolean { return !equals(this.objectInfo, this._originalObjectInfo, false); } } diff --git a/extensions/mssql/src/objectManagement/ui/serverRoleDialog.ts b/extensions/mssql/src/objectManagement/ui/serverRoleDialog.ts index 5cb7c73fd0..68299741ec 100644 --- a/extensions/mssql/src/objectManagement/ui/serverRoleDialog.ts +++ b/extensions/mssql/src/objectManagement/ui/serverRoleDialog.ts @@ -30,7 +30,7 @@ export class ServerRoleDialog extends ObjectManagementDialogBase { private generalSection: azdata.GroupContainer; @@ -34,7 +34,7 @@ export class UserDialog extends ObjectManagementDialogBase(obj: T): T { - if (!obj || typeof obj !== 'object') { - return obj; - } - if (obj instanceof RegExp) { - // See https://github.com/Microsoft/TypeScript/issues/10990 - return obj as any; - } - const result: any = Array.isArray(obj) ? [] : {}; - Object.keys(obj).forEach((key: string) => { - if ((obj)[key] && typeof (obj)[key] === 'object') { - result[key] = deepClone((obj)[key]); - } else { - result[key] = (obj)[key]; - } - }); - return result; -} - export async function refreshParentNode(context: azdata.ObjectExplorerContext): Promise { if (context) { try { diff --git a/extensions/mssql/src/objectManagement/ui/dialogBase.ts b/extensions/mssql/src/ui/dialogBase.ts similarity index 96% rename from extensions/mssql/src/objectManagement/ui/dialogBase.ts rename to extensions/mssql/src/ui/dialogBase.ts index 7a31df1f74..9b7601c4a0 100644 --- a/extensions/mssql/src/objectManagement/ui/dialogBase.ts +++ b/extensions/mssql/src/ui/dialogBase.ts @@ -6,7 +6,7 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { EOL } from 'os'; -import * as localizedConstants from '../localizedConstants'; +import * as uiLoc from '../ui/localizedConstants'; export const DefaultLabelWidth = 150; export const DefaultInputWidth = 300; @@ -39,7 +39,7 @@ export abstract class DialogBase { constructor(title: string, name: string, width: azdata.window.DialogWidth = 'narrow', style: azdata.window.DialogStyle = 'flyout') { this.dialogObject = azdata.window.createModelViewDialog(title, name, width, style); - this.dialogObject.okButton.label = localizedConstants.OkText; + this.dialogObject.okButton.label = uiLoc.OkText; this.dialogObject.registerCloseValidator(async (): Promise => { const confirmed = await this.onConfirmation(); if (!confirmed) { @@ -83,7 +83,7 @@ export abstract class DialogBase { this._formContainer = this.createFormContainer([]); this._loadingComponent = view.modelBuilder.loadingComponent().withItem(this._formContainer).withProps({ loading: true, - loadingText: localizedConstants.LoadingDialogText, + loadingText: uiLoc.LoadingDialogText, showText: true, CSSStyles: { width: "100%", @@ -179,7 +179,7 @@ export abstract class DialogBase { data: data, columns: [ { - value: localizedConstants.SelectedText, + value: uiLoc.SelectedText, type: azdata.ColumnType.checkBox, options: { actionOnCheckbox: azdata.ActionOnCellCheckboxCheck.customAction } }, ...columnNames.map(name => { @@ -240,11 +240,11 @@ export abstract class DialogBase { const updateButtons = () => { removeButton.enabled = table.selectedRows.length > 0; } - addButton = this.createButton(localizedConstants.AddText, addButtonAriaLabel, async () => { + addButton = this.createButton(uiLoc.AddText, addButtonAriaLabel, async () => { await addHandler(); updateButtons(); }); - removeButton = this.createButton(localizedConstants.RemoveText, removeButtonAriaLabel, async () => { + removeButton = this.createButton(uiLoc.RemoveText, removeButtonAriaLabel, async () => { await removeHandler(); updateButtons(); }, false); diff --git a/extensions/mssql/src/ui/localizedConstants.ts b/extensions/mssql/src/ui/localizedConstants.ts new file mode 100644 index 0000000000..3fa0a32c93 --- /dev/null +++ b/extensions/mssql/src/ui/localizedConstants.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +export const HelpText: string = localize('mssql.ui.helpText', "Help"); +export const YesText: string = localize('mssql.ui.yesText', "Yes"); +export const OkText: string = localize('mssql.ui.OkText', "OK"); +export const LoadingDialogText: string = localize('mssql.ui.loadingDialog', "Loading dialog..."); +export const ScriptText: string = localize('mssql.ui.scriptText', "Script"); +export const SelectedText = localize('objectManagement.selectedLabel', "Selected"); +export const AddText = localize('objectManagement.addText', "Add…"); +export const RemoveText = localize('objectManagement.removeText', "Remove"); +export const NoActionScriptedMessage: string = localize('mssql.ui.noActionScriptedMessage', "There is no action to be scripted."); +export const ScriptGeneratedText: string = localize('mssql.ui.scriptGenerated', "Script has been generated successfully. You can close the dialog to view it in the newly opened editor.") + +export function scriptError(error: string): string { + return localize('mssql.ui.scriptError', "An error occurred while generating the script. {0}", error); +} diff --git a/extensions/mssql/src/ui/scriptableDialogBase.ts b/extensions/mssql/src/ui/scriptableDialogBase.ts new file mode 100644 index 0000000000..9bce48e5fc --- /dev/null +++ b/extensions/mssql/src/ui/scriptableDialogBase.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; +import * as vscode from 'vscode'; +import * as localizedConstants from './localizedConstants'; +import { DialogBase } from './dialogBase'; +import { getErrorMessage } from '../utils'; +import { providerId } from '../constants'; + +export interface ScriptableDialogOptions { + /** + * The width of the dialog, defaults to narrow if not set + */ + width?: azdata.window.DialogWidth; +} + +/** + * Base class for a scriptable dialog - that is a dialog that has a "Script" button which will + * open a new editor with the generated script when clicked. This also includes a "Help" button + * to open up a given URL when clicked. + */ +export abstract class ScriptableDialogBase extends DialogBase { + private _helpButton: azdata.window.Button; + private _scriptButton: azdata.window.Button; + + constructor(title: string, name: string, protected readonly options: OptionsType) { + super(title, name, options.width || 'narrow', 'flyout' + ); + this._helpButton = azdata.window.createButton(localizedConstants.HelpText, 'left'); + this.disposables.push(this._helpButton.onClick(async () => { + await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(this.helpUrl)); + })); + this._scriptButton = azdata.window.createButton(localizedConstants.ScriptText, 'left'); + this.disposables.push(this._scriptButton.onClick(async () => { await this.onScriptButtonClick(); })); + this.dialogObject.customButtons = [this._helpButton, this._scriptButton]; + } + + /** + * Called after initializeData to initialize the UI components of the dialog. + */ + protected abstract initializeUI(): Promise; + + /** + * Called before initializeUI to initialize the data for the dialog. + */ + protected abstract initializeData(): Promise; + + /** + * The URL to open when the Help button is clicked + */ + protected abstract get helpUrl(): string; + + /** + * Whether the dialog is currently dirty, which will control what buttons are enabled. + */ + protected abstract get isDirty(): boolean; + + protected override onFormFieldChange(): void { + this._scriptButton.enabled = this.isDirty; + this.dialogObject.okButton.enabled = this.isDirty; + } + + protected override async initialize(): Promise { + await this.initializeData(); + await this.initializeUI(); + } + + protected override onLoadingStatusChanged(isLoading: boolean): void { + super.onLoadingStatusChanged(isLoading); + this._helpButton.enabled = !isLoading; + this.dialogObject.okButton.enabled = this._scriptButton.enabled = isLoading ? false : this.isDirty; + } + + /** + * Called when the script button is clicked, returns the script that will be opened up in a new editor. + */ + protected abstract generateScript(): Promise; + + private async onScriptButtonClick(): Promise { + this.onLoadingStatusChanged(true); + try { + const isValid = await this.runValidation(); + if (!isValid) { + return; + } + let message: string; + const script = await this.generateScript(); + if (script) { + message = localizedConstants.ScriptGeneratedText; + await azdata.queryeditor.openQueryDocument({ content: script }, providerId); + } else { + message = localizedConstants.NoActionScriptedMessage; + } + this.dialogObject.message = { + text: message, + level: azdata.window.MessageLevel.Information + }; + } catch (err) { + this.dialogObject.message = { + text: localizedConstants.scriptError(getErrorMessage(err)), + level: azdata.window.MessageLevel.Error + }; + } finally { + this.onLoadingStatusChanged(false); + } + } +} diff --git a/extensions/mssql/src/util/objects.ts b/extensions/mssql/src/util/objects.ts index e2dc1febbc..b49f7240aa 100644 --- a/extensions/mssql/src/util/objects.ts +++ b/extensions/mssql/src/util/objects.ts @@ -68,3 +68,22 @@ export function equals(one: any, other: any, strictArrayCompare: boolean = true) } return true; } + +export function deepClone(obj: T): T { + if (!obj || typeof obj !== 'object') { + return obj; + } + if (obj instanceof RegExp) { + // See https://github.com/Microsoft/TypeScript/issues/10990 + return obj as any; + } + const result: any = Array.isArray(obj) ? [] : {}; + Object.keys(obj).forEach((key: string) => { + if ((obj)[key] && typeof (obj)[key] === 'object') { + result[key] = deepClone((obj)[key]); + } else { + result[key] = (obj)[key]; + } + }); + return result; +}