diff --git a/extensions/sql-database-projects/images/dark/refresh.svg b/extensions/sql-database-projects/images/dark/refresh.svg new file mode 100644 index 0000000000..f03579110b --- /dev/null +++ b/extensions/sql-database-projects/images/dark/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/sql-database-projects/images/light/refresh.svg b/extensions/sql-database-projects/images/light/refresh.svg new file mode 100644 index 0000000000..f03579110b --- /dev/null +++ b/extensions/sql-database-projects/images/light/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index 658470b9d9..bc82b9315e 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -79,8 +79,9 @@ export const noDataSourcesText = localize('noDataSourcesText', "No data sources export const loadProfileButtonText = localize('loadProfileButtonText', "Load Profile..."); export const profileReadError = localize('profileReadError', "Could not load the profile file."); export const sqlCmdTableLabel = localize('sqlCmdTableLabel', "SQLCMD Variables"); -export const sqlCmdVariableColumn = localize('sqlCmdVariableColumn', "Variable"); +export const sqlCmdVariableColumn = localize('sqlCmdVariableColumn', "Name"); export const sqlCmdValueColumn = localize('sqlCmdValueColumn', "Value"); +export const loadSqlCmdVarsButtonTitle = localize('reloadValuesFromProjectButtonTitle', "Reload values from project"); // Error messages diff --git a/extensions/sql-database-projects/src/common/iconHelper.ts b/extensions/sql-database-projects/src/common/iconHelper.ts index 900b688a04..4034daf158 100644 --- a/extensions/sql-database-projects/src/common/iconHelper.ts +++ b/extensions/sql-database-projects/src/common/iconHelper.ts @@ -20,6 +20,8 @@ export class IconPathHelper { public static referenceGroup: IconPath; public static referenceDatabase: IconPath; + public static refresh: IconPath; + public static setExtensionContext(extensionContext: vscode.ExtensionContext) { IconPathHelper.extensionContext = extensionContext; @@ -30,6 +32,8 @@ export class IconPathHelper { IconPathHelper.referenceGroup = IconPathHelper.makeIcon('referenceGroup'); IconPathHelper.referenceDatabase = IconPathHelper.makeIcon('reference-database'); + + IconPathHelper.refresh = IconPathHelper.makeIcon('refresh'); } private static makeIcon(name: string) { diff --git a/extensions/sql-database-projects/src/common/uiConstants.ts b/extensions/sql-database-projects/src/common/uiConstants.ts new file mode 100644 index 0000000000..fb41ad494b --- /dev/null +++ b/extensions/sql-database-projects/src/common/uiConstants.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// CSS Styles +export namespace cssStyles { + export const text = { 'user-select': 'text', 'cursor': 'text' }; + export const tableHeader = { ...text, 'text-align': 'left', 'border': 'none', 'font-size': '12px', 'font-weight': 'normal', 'color': '#666666' }; + export const tableRow = { ...text, 'border-top': 'solid 1px #ccc', 'border-bottom': 'solid 1px #ccc', 'border-left': 'none', 'border-right': 'none', 'font-size': '12px' }; + export const titleFontSize = 13; +} diff --git a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts index a0b5ba9b9f..dd7daa50fc 100644 --- a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts @@ -12,8 +12,8 @@ import { Project } from '../models/project'; import { SqlConnectionDataSource } from '../models/dataSources/sqlConnectionStringSource'; import { IPublishSettings, IGenerateScriptSettings } from '../models/IPublishSettings'; import { DeploymentOptions } from '../../../mssql/src/mssql'; - -const titleFontSize = 12; +import { IconPathHelper } from '../common/iconHelper'; +import { cssStyles } from '../common/uiConstants'; interface DataSourceDropdownValue extends azdata.CategoryValue { dataSource: SqlConnectionDataSource; @@ -31,13 +31,14 @@ export class PublishDatabaseDialog { private connectionsRadioButton: azdata.RadioButtonComponent | undefined; private dataSourcesRadioButton: azdata.RadioButtonComponent | undefined; private loadProfileButton: azdata.ButtonComponent | undefined; - private sqlCmdVariablesTable: azdata.TableComponent | undefined; - private sqlCmdVariablesFormComponent: azdata.FormComponent | undefined; + private sqlCmdVariablesTable: azdata.DeclarativeTableComponent | undefined; + private sqlCmdVariablesFormComponentGroup: azdata.FormComponentGroup | undefined; + private loadSqlCmdVarsButton: azdata.ButtonComponent | undefined; private formBuilder: azdata.FormBuilder | undefined; private connectionId: string | undefined; private connectionIsDataSource: boolean | undefined; - private profileSqlCmdVars: Record | undefined; + private sqlCmdVars: Record | undefined; private deploymentOptions: DeploymentOptions | undefined; private toDispose: vscode.Disposable[] = []; @@ -97,23 +98,21 @@ export class PublishDatabaseDialog { }); this.loadProfileButton = this.createLoadProfileButton(view); - this.sqlCmdVariablesTable = view.modelBuilder.table().withProperties({ - title: constants.sqlCmdTableLabel, - data: this.convertSqlCmdVarsToTableFormat(this.project.sqlCmdVariables), - columns: [ + this.sqlCmdVariablesTable = this.createSqlCmdTable(view); + this.loadSqlCmdVarsButton = this.createLoadSqlCmdVarsButton(view); + + this.sqlCmdVariablesFormComponentGroup = { + components: [ { - value: constants.sqlCmdVariableColumn + title: '', + component: this.loadSqlCmdVarsButton }, { - value: constants.sqlCmdValueColumn, - }], - width: 400, - height: 400 - }).component(); - - this.sqlCmdVariablesFormComponent = { - title: constants.sqlCmdTableLabel, - component: this.sqlCmdVariablesTable + title: '', + component: this.sqlCmdVariablesTable + } + ], + title: constants.sqlCmdTableLabel }; this.formBuilder = view.modelBuilder.formContainer() @@ -146,7 +145,7 @@ export class PublishDatabaseDialog { // add SQLCMD variables table if the project has any if (Object.keys(this.project.sqlCmdVariables).length > 0) { - this.formBuilder.addFormItem(this.sqlCmdVariablesFormComponent, { titleFontSize: titleFontSize }); + this.formBuilder.addFormItem(this.sqlCmdVariablesFormComponentGroup, { titleFontSize: cssStyles.titleFontSize }); } let formModel = this.formBuilder.component(); @@ -220,14 +219,8 @@ export class PublishDatabaseDialog { } private getSqlCmdVariablesForPublish(): Record { - // get SQLCMD variables from project - let sqlCmdVariables = { ...this.project.sqlCmdVariables }; - - // update with SQLCMD variables loaded from profile if there are any - for (const key in this.profileSqlCmdVars) { - sqlCmdVariables[key] = this.profileSqlCmdVars[key]; - } - + // get SQLCMD variables from table + let sqlCmdVariables = { ...this.sqlCmdVars }; return sqlCmdVariables; } @@ -346,6 +339,69 @@ export class PublishDatabaseDialog { } } + private createSqlCmdTable(view: azdata.ModelView): azdata.DeclarativeTableComponent { + this.sqlCmdVars = { ...this.project.sqlCmdVariables }; + + const table = view.modelBuilder.declarativeTable().withProperties({ + ariaLabel: constants.sqlCmdTableLabel, + data: this.convertSqlCmdVarsToTableFormat(this.sqlCmdVars), + columns: [ + { + displayName: constants.sqlCmdVariableColumn, + valueType: azdata.DeclarativeDataType.string, + width: '50%', + isReadOnly: true, + headerCssStyles: cssStyles.tableHeader, + rowCssStyles: cssStyles.tableRow + }, + { + displayName: constants.sqlCmdValueColumn, + valueType: azdata.DeclarativeDataType.string, + width: '50%', + isReadOnly: false, + headerCssStyles: cssStyles.tableHeader, + rowCssStyles: cssStyles.tableRow + }], + width: '100%' + }).component(); + + table.onDataChanged(() => { + this.sqlCmdVars = {}; + table.data.forEach((row) => { + (>this.sqlCmdVars)[row[0]] = row[1]; + }); + + this.tryEnableGenerateScriptAndOkButtons(); + }); + + return table; + } + + private createLoadSqlCmdVarsButton(view: azdata.ModelView): azdata.ButtonComponent { + let loadSqlCmdVarsButton: azdata.ButtonComponent = view.modelBuilder.button().withProperties({ + label: constants.loadSqlCmdVarsButtonTitle, + title: constants.loadSqlCmdVarsButtonTitle, + ariaLabel: constants.loadSqlCmdVarsButtonTitle, + width: '210px', + iconPath: IconPathHelper.refresh, + height: '18px', + CSSStyles: { 'font-size': '13px' } + }).component(); + + loadSqlCmdVarsButton.onDidClick(async () => { + this.sqlCmdVars = { ...this.project.sqlCmdVariables }; + + const data = this.convertSqlCmdVarsToTableFormat(this.getSqlCmdVariablesForPublish()); + await (this.sqlCmdVariablesTable).updateProperties({ + data: data + }); + + this.tryEnableGenerateScriptAndOkButtons(); + }); + + return loadSqlCmdVarsButton; + } + private createEditConnectionButton(view: azdata.ModelView): azdata.Component { let editConnectionButton: azdata.ButtonComponent = view.modelBuilder.button().withProperties({ label: constants.editConnectionButtonText, @@ -419,22 +475,25 @@ export class PublishDatabaseDialog { this.connectionId = result.connectionId; (this.targetConnectionTextBox).value = result.connectionString; - this.deploymentOptions = result.options; - this.profileSqlCmdVars = result.sqlCmdVariables; - const data = this.convertSqlCmdVarsToTableFormat(this.getSqlCmdVariablesForPublish()); + for (let key in result.sqlCmdVariables) { + (>this.sqlCmdVars)[key] = result.sqlCmdVariables[key]; + } - await (this.sqlCmdVariablesTable).updateProperties({ + this.deploymentOptions = result.options; + + const data = this.convertSqlCmdVarsToTableFormat(this.getSqlCmdVariablesForPublish()); + await (this.sqlCmdVariablesTable).updateProperties({ data: data }); if (Object.keys(result.sqlCmdVariables).length) { // add SQLCMD Variables table if it wasn't there before if (Object.keys(this.project.sqlCmdVariables).length === 0) { - this.formBuilder?.addFormItem(this.sqlCmdVariablesFormComponent, { titleFontSize: titleFontSize }); + this.formBuilder?.addFormItem(this.sqlCmdVariablesFormComponentGroup, { titleFontSize: cssStyles.titleFontSize }); } } else if (Object.keys(this.project.sqlCmdVariables).length === 0) { // remove the table if there are no SQLCMD variables in the project and loaded profile - this.formBuilder?.removeFormItem(this.sqlCmdVariablesFormComponent); + this.formBuilder?.removeFormItem(this.sqlCmdVariablesFormComponentGroup); } } }); @@ -453,8 +512,9 @@ export class PublishDatabaseDialog { // only enable Generate Script and Ok buttons if all fields are filled private tryEnableGenerateScriptAndOkButtons(): void { - if (this.targetConnectionTextBox!.value && this.targetDatabaseTextBox!.value - || this.connectionIsDataSource && this.targetDatabaseTextBox!.value) { + if ((this.targetConnectionTextBox!.value && this.targetDatabaseTextBox!.value + || this.connectionIsDataSource && this.targetDatabaseTextBox!.value) + && this.allSqlCmdVariablesFilled()) { this.dialog.okButton.enabled = true; this.dialog.customButtons[0].enabled = true; } else { @@ -462,4 +522,14 @@ export class PublishDatabaseDialog { this.dialog.customButtons[0].enabled = false; } } + + private allSqlCmdVariablesFilled(): boolean { + for (let key in this.sqlCmdVars) { + if (this.sqlCmdVars[key] === '' || this.sqlCmdVars[key] === undefined) { + return false; + } + } + + return true; + } } diff --git a/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts b/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts index 08de8a33f2..44d3b8cc8a 100644 --- a/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts +++ b/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts @@ -52,7 +52,6 @@ export async function load(profileUri: Uri, dacfxService: mssql.IDacFxService): }; } - async function readConnectionString(xmlDoc: any): Promise<{ connectionId: string, connectionString: string }> { let targetConnectionString: string = ''; let connId: string = '';