diff --git a/extensions/dacpac/src/test/testDacFxService.ts b/extensions/dacpac/src/test/testDacFxService.ts index 7da99b9765..e10d604854 100644 --- a/extensions/dacpac/src/test/testDacFxService.ts +++ b/extensions/dacpac/src/test/testDacFxService.ts @@ -68,6 +68,10 @@ export class DacFxTestService implements mssql.IDacFxService { booleanOptionsDictionary: { 'SampleProperty1': { value: false, description: sampleDesc, displayName: sampleName }, 'SampleProperty2': { value: false, description: sampleDesc, displayName: sampleName } + }, + objectTypesDictionary: { + 'ObjectType1': sampleName, + 'ObjectType2': sampleName } } }; diff --git a/extensions/mssql/config.json b/extensions/mssql/config.json index 161e2cd237..c51aef5292 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.2.0.8", + "version": "4.2.0.10", "downloadFileNames": { "Windows_86": "win-x86-net6.0.zip", "Windows_64": "win-x64-net6.0.zip", diff --git a/extensions/mssql/src/mssql.d.ts b/extensions/mssql/src/mssql.d.ts index cf88230c2d..0dcfe74b19 100644 --- a/extensions/mssql/src/mssql.d.ts +++ b/extensions/mssql/src/mssql.d.ts @@ -163,10 +163,10 @@ declare module 'mssql' { } /** - * Interface containing deployment options of integer type, value property holds values from \Product\Source\DeploymentApi\ObjectTypes.cs enum + * Interface containing deployment options of string[] type, value property holds enum names (nothing but option name) from \Product\Source\DeploymentApi\ObjectTypes.cs enum */ export interface DacDeployOptionPropertyObject { - value: number[]; + value: string[]; description: string; displayName: string; } @@ -179,6 +179,8 @@ declare module 'mssql' { excludeObjectTypes: DacDeployOptionPropertyObject; // key will be the boolean option name booleanOptionsDictionary: { [key: string]: DacDeployOptionPropertyBoolean }; + // key will be the object type enum name(nothing but option name) + objectTypesDictionary: { [key: string]: string }; } /* diff --git a/extensions/schema-compare/src/test/testUtils.ts b/extensions/schema-compare/src/test/testUtils.ts index 6ef25e2b99..1131277e9e 100644 --- a/extensions/schema-compare/src/test/testUtils.ts +++ b/extensions/schema-compare/src/test/testUtils.ts @@ -117,6 +117,10 @@ export function getDeploymentOptions(): mssql.DeploymentOptions { booleanOptionsDictionary: { 'SampleDisplayOption1': { value: false, description: sampleDesc, displayName: sampleName }, 'SampleDisplayOption2': { value: false, description: sampleDesc, displayName: sampleName } + }, + includeObjectsDictionary: { + 'SampleProperty1': sampleName, + 'SampleProperty2': sampleName } }; } diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index fa20fc0310..20470cbb61 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -149,11 +149,12 @@ export const nameMustNotBeEmpty = localize('nameMustNotBeEmpty', "Name must not export const AdvancedOptionsButton = localize('advancedOptionsButton', 'Advanced...'); export const AdvancedPublishOptions = localize('advancedPublishOptions', 'Advanced Publish Options'); export const PublishOptions = localize('publishOptions', 'Publish Options'); +export const ExcludeObjectTypeTab = localize('excludeObjectTypes', 'Exclude Object Types'); export const ResetButton: string = localize('reset', "Reset"); export const OptionDescription: string = localize('optionDescription', "Option Description"); export const OptionName: string = localize('optionName', "Option Name"); -export const OptionInclude: string = localize('Include', "Include"); -export function OptionNotFoundWarningMessage(label: string) { return localize('OptionNotFoundWarningMessage', "label: {0} does not exist in the options value name lookup", label); } +export const OptionInclude: string = localize('include', "Include"); +export function OptionNotFoundWarningMessage(label: string) { return localize('optionNotFoundWarningMessage', "label: {0} does not exist in the options value name lookup", label); } // Deploy export const SqlServerName = 'SQL server'; diff --git a/extensions/sql-database-projects/src/common/utils.ts b/extensions/sql-database-projects/src/common/utils.ts index 1a361ec9aa..0331318f34 100644 --- a/extensions/sql-database-projects/src/common/utils.ts +++ b/extensions/sql-database-projects/src/common/utils.ts @@ -325,13 +325,6 @@ export async function defaultAzureAccountServiceFactory(): Promise { const schemaCompareService = await getSchemaCompareService(); const result = await schemaCompareService.schemaCompareGetDefaultOptions(); - // re-include database-scoped credentials - if (getAzdataApi()) { - result.defaultDeploymentOptions.excludeObjectTypes.value = (result.defaultDeploymentOptions as mssql.DeploymentOptions).excludeObjectTypes.value?.filter(x => x !== mssql.SchemaObjectType.DatabaseScopedCredentials); - } else { - result.defaultDeploymentOptions.excludeObjectTypes.value = (result.defaultDeploymentOptions as vscodeMssql.DeploymentOptions).excludeObjectTypes.value?.filter(x => x !== vscodeMssql.SchemaObjectType.DatabaseScopedCredentials); - } - // this option needs to be true for same database references validation to work if (project.databaseReferences.length > 0) { result.defaultDeploymentOptions.booleanOptionsDictionary.includeCompositeObjects.value = true; diff --git a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts index 76fe83d589..0f7549c373 100644 --- a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts @@ -145,7 +145,6 @@ export class PublishDatabaseDialog { this.connectionRow = this.createConnectionRow(view); this.databaseRow = this.createDatabaseRow(view); const displayOptionsButton = this.createOptionsButton(view); - displayOptionsButton.enabled = false; const horizontalFormSection = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); horizontalFormSection.addItems([profileRow, this.databaseRow]); @@ -172,12 +171,10 @@ export class PublishDatabaseDialog { title: constants.selectConnectionRadioButtonsTitle, component: selectConnectionRadioButtons },*/ - /* TODO : Disabling deployment options for the July release { component: displayOptionsButton, title: '' } - */ ] } ], { @@ -938,7 +935,12 @@ export class PublishDatabaseDialog { * Gets the default deployment options from the dacfx service */ public async getDefaultDeploymentOptions(): Promise { - return await utils.getDefaultPublishDeploymentOptions(this.project) as DeploymentOptions; + const defaultDeploymentOptions = await utils.getDefaultPublishDeploymentOptions(this.project) as DeploymentOptions; + if (defaultDeploymentOptions && defaultDeploymentOptions.excludeObjectTypes !== undefined) { + // For publish dialog no default exclude options should exists + defaultDeploymentOptions.excludeObjectTypes.value = []; + } + return defaultDeploymentOptions; } /* diff --git a/extensions/sql-database-projects/src/dialogs/publishOptionsDialog.ts b/extensions/sql-database-projects/src/dialogs/publishOptionsDialog.ts index 159bd11311..5b4b4452c5 100644 --- a/extensions/sql-database-projects/src/dialogs/publishOptionsDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/publishOptionsDialog.ts @@ -24,6 +24,9 @@ export class PublishOptionsDialog { private optionsFlexBuilder: azdataType.FlexContainer | undefined; private optionsChanged: boolean = false; private isResetOptionsClicked: boolean = false; + private excludeObjectTypesOptionsTab: azdataType.window.DialogTab | undefined; + private excludeObjectTypesOptionsTable: azdataType.TableComponent | undefined; + private excludeObjectTypesOptionsFlexBuilder: azdataType.FlexContainer | undefined; constructor(defaultOptions: mssql.DeploymentOptions, private publish: PublishDatabaseDialog) { this.optionsModel = new DeployOptionsModel(defaultOptions); @@ -31,8 +34,10 @@ export class PublishOptionsDialog { protected initializeDialog(): void { this.optionsTab = utils.getAzdataApi()!.window.createTab(constants.PublishOptions); - this.intializeDeploymentOptionsDialogTab(); - this.dialog.content = [this.optionsTab]; + this.excludeObjectTypesOptionsTab = utils.getAzdataApi()!.window.createTab(constants.ExcludeObjectTypeTab); + this.initializeDeploymentOptionsDialogTab(); + this.initializeExcludeObjectTypesOptionsDialogTab(); + this.dialog.content = [this.optionsTab, this.excludeObjectTypesOptionsTab]; } public openDialog(): void { @@ -55,7 +60,7 @@ export class PublishOptionsDialog { utils.getAzdataApi()!.window.openDialog(this.dialog); } - private intializeDeploymentOptionsDialogTab(): void { + private initializeDeploymentOptionsDialogTab(): void { this.optionsTab?.registerContent(async view => { this.descriptionHeading = view.modelBuilder.table().withProps({ data: [], @@ -113,6 +118,34 @@ export class PublishOptionsDialog { }); } + private initializeExcludeObjectTypesOptionsDialogTab(): void { + this.excludeObjectTypesOptionsTab?.registerContent(async view => { + this.excludeObjectTypesOptionsTable = view.modelBuilder.table().component(); + await this.updateExcludeObjectsTable(); + + // Update exclude type options value on checkbox onchange + this.disposableListeners.push(this.excludeObjectTypesOptionsTable!.onCellAction!((rowState) => { + const checkboxState = rowState; + if (checkboxState && checkboxState.row !== undefined) { + // data[row][1] contains the exclude type option display name + const displayName = this.excludeObjectTypesOptionsTable?.data[checkboxState.row][1]; + this.optionsModel.setExcludeObjectTypesOptionValue(displayName, checkboxState.checked); + this.optionsChanged = true; + // customButton[0] is the reset button, enabling it when option checkbox is changed + this.dialog.customButtons[0].enabled = true; + } + })); + + this.excludeObjectTypesOptionsFlexBuilder = view.modelBuilder.flexContainer() + .withLayout({ + flexFlow: 'column' + }).component(); + + this.excludeObjectTypesOptionsFlexBuilder.addItem(this.excludeObjectTypesOptionsTable, { CSSStyles: { 'overflow': 'scroll', 'height': '80vh', 'padding-top': '2px' } }); + await view.initializeModel(this.excludeObjectTypesOptionsFlexBuilder); + }); + } + /* * Update the default options to the options table area */ @@ -141,12 +174,41 @@ export class PublishOptionsDialog { }); } + /* + * Update the default options to the exclude objects table area + */ + private async updateExcludeObjectsTable(): Promise { + const data = this.optionsModel.getExcludeObjectTypesOptionsData(); + await this.excludeObjectTypesOptionsTable?.updateProperties({ + data: data, + columns: [ + + { + value: constants.OptionInclude, + type: utils.getAzdataApi()!.ColumnType.checkBox, + action: utils.getAzdataApi()!.ActionOnCellCheckboxCheck.customAction, + headerCssClass: 'display-none', + cssClass: 'no-borders align-with-header', + width: 50 + }, + { + value: constants.OptionName, + headerCssClass: 'display-none', + cssClass: 'no-borders align-with-header', + width: 50 + } + ], + ariaRowCount: data.length + }); + } + /* * Ok button click, will update the deployment options with selections */ protected execute(): void { - // Update the model deploymentoptions with the updated table component values + // Update the model deploymentoptions with the updated options/excludeObjects table component values this.optionsModel.setDeploymentOptions(); + this.optionsModel.setExcludeObjectTypesToDeploymentOptions(); // Set the publish deploymentoptions with the updated table component values this.publish.setDeploymentOptions(this.optionsModel.deploymentOptions); this.disposeListeners(); @@ -173,14 +235,19 @@ export class PublishOptionsDialog { const result = await this.publish.getDefaultDeploymentOptions(); this.optionsModel.deploymentOptions = result; - // reset optionsvalueNameLookup with default deployment options + // reset optionsvalueNameLookup and excludeObjectTypesLookup with default deployment options this.optionsModel.setOptionsToValueNameLookup(); + this.optionsModel.setExcludeObjectTypesLookup(); await this.updateOptionsTable(); this.optionsFlexBuilder?.removeItem(this.optionsTable!); this.optionsFlexBuilder?.insertItem(this.optionsTable!, 0, { CSSStyles: { 'overflow': 'scroll', 'height': '65vh', 'padding-top': '2px' } }); TelemetryReporter.sendActionEvent(TelemetryViews.PublishOptionsDialog, TelemetryActions.resetOptions); + await this.updateExcludeObjectsTable(); + this.excludeObjectTypesOptionsFlexBuilder?.removeItem(this.excludeObjectTypesOptionsTable!); + this.excludeObjectTypesOptionsFlexBuilder?.addItem(this.excludeObjectTypesOptionsTable!, { CSSStyles: { 'overflow': 'scroll', 'height': '80vh', 'padding-top': '2px' } }); + // setting optionsChanged to false when reset click, if optionsChanged is true during execute, that means there is an option changed after reset this.isResetOptionsClicked = true; this.optionsChanged = false; diff --git a/extensions/sql-database-projects/src/models/options/deployOptionsModel.ts b/extensions/sql-database-projects/src/models/options/deployOptionsModel.ts index 411693289f..5abbd8391c 100644 --- a/extensions/sql-database-projects/src/models/options/deployOptionsModel.ts +++ b/extensions/sql-database-projects/src/models/options/deployOptionsModel.ts @@ -10,9 +10,11 @@ import * as constants from '../../common/constants'; export class DeployOptionsModel { // key is the option display name and values are checkboxValue and optionName private optionsValueNameLookup: { [key: string]: mssql.IOptionWithValue } = {}; + private excludeObjectTypesLookup: { [key: string]: mssql.IOptionWithValue } = {}; constructor(public deploymentOptions: mssql.DeploymentOptions) { this.setOptionsToValueNameLookup(); + this.setExcludeObjectTypesLookup(); } /* @@ -69,4 +71,62 @@ export class DeployOptionsModel { } return optionName !== undefined ? this.deploymentOptions.booleanOptionsDictionary[optionName.optionName].description : ''; } + + /* + * Sets exclude object types option's checkbox values and property name to the excludeObjectTypesLookup map + */ + public setExcludeObjectTypesLookup(): void { + Object.entries(this.deploymentOptions.objectTypesDictionary).forEach(option => { + const optionValue: mssql.IOptionWithValue = { + optionName: option[0], + checked: this.getExcludeObjectTypeOptionCheckStatus(option[0]) + }; + this.excludeObjectTypesLookup[option[1]] = optionValue; + }); + } + + /* + * Initialize options data from objectTypesDictionary for table component + * Returns data as [booleanValue, optionName] + */ + public getExcludeObjectTypesOptionsData(): any[][] { + let data: any[][] = []; + Object.entries(this.deploymentOptions.objectTypesDictionary).forEach(option => { + // option[1] is the display name and option[0] is the optionName + data.push([this.getExcludeObjectTypeOptionCheckStatus(option[0]), option[1]]); + }); + + return data.sort((a, b) => a[1].localeCompare(b[1])); + } + + /* + * Gets the selected/default value of the object type option + * return true for the deploymentOptions.excludeObjectTypes option, if it is in ObjectTypesDictionary + */ + public getExcludeObjectTypeOptionCheckStatus(optionName: string): boolean { + return (this.deploymentOptions.excludeObjectTypes.value?.find(x => x.toLowerCase() === optionName.toLowerCase())) !== undefined ? true : false; + } + + /* + * Sets the checkbox value to the excludeObjectTypesLookup map + */ + public setExcludeObjectTypesOptionValue(displayName: string, checked: boolean): void { + this.excludeObjectTypesLookup[displayName].checked = checked; + } + + /* + * Sets the selected option checkbox value to the deployment options + */ + public setExcludeObjectTypesToDeploymentOptions(): void { + let finalExcludedObjectTypes: string[] = []; + Object.entries(this.excludeObjectTypesLookup).forEach(option => { + // option[1] holds checkedbox value and optionName + if (option[1].checked) { + finalExcludedObjectTypes.push(option[1].optionName); + } + }); + + this.deploymentOptions.excludeObjectTypes.value = finalExcludedObjectTypes; + } + } diff --git a/extensions/sql-database-projects/src/test/models/deployOptionsModel.test.ts b/extensions/sql-database-projects/src/test/models/deployOptionsModel.test.ts index 7d46ef13ad..cded3d2889 100644 --- a/extensions/sql-database-projects/src/test/models/deployOptionsModel.test.ts +++ b/extensions/sql-database-projects/src/test/models/deployOptionsModel.test.ts @@ -19,7 +19,7 @@ describe('Publish Dialog Deploy Options Model', () => { const model = new DeployOptionsModel(testUtils.getDeploymentOptions()); Object.entries(model.deploymentOptions.booleanOptionsDictionary).forEach(option => { // option[1] contains the value, description and displayName - should(model.getOptionDescription(option[1].displayName)).not.equal(undefined); + should(model.getOptionDescription(option[1].displayName)).not.equal(undefined, 'publish option description should not be undefined'); }); }); @@ -27,4 +27,31 @@ describe('Publish Dialog Deploy Options Model', () => { const model = new DeployOptionsModel(testUtils.getDeploymentOptions()); should(model.getOptionDescription('')).equal(''); }); + + + it('Should have no default exclude object types', function (): void { + const model = new DeployOptionsModel(testUtils.getDeploymentOptions()); + should(model.deploymentOptions.excludeObjectTypes.value.length).be.equal(0, 'There should be no object types excluded from excludeObjectTypes'); + + // should return true for all object type options as there are no default excludeObjectTypes in the deployment options + Object.keys(model.deploymentOptions.objectTypesDictionary).forEach(option => { + should(model.getExcludeObjectTypeOptionCheckStatus(option)).equal(false, 'excludeObjectTypes property should be empty by default and return false'); + }); + }); + + it('Should have atleast one default exclude object types', function (): void { + const model = new DeployOptionsModel(testUtils.getDeploymentOptions()); + model.deploymentOptions.excludeObjectTypes.value = ['SampleProperty1']; + + should(model.deploymentOptions.excludeObjectTypes.value.length).be.equal(1, 'There should be one excluded object'); + + // should return true for all exclude object types options and false for the exising defauit option + Object.keys(model.deploymentOptions.objectTypesDictionary).forEach(option => { + if (option === 'SampleProperty1') { + should(model.getExcludeObjectTypeOptionCheckStatus(option)).equal(true, 'should return true for the excludeObjectTypes SampleProperty1 '); + } else { + should(model.getExcludeObjectTypeOptionCheckStatus(option)).equal(false, 'should return false for all excludeObjectTypes property as it is empty'); + } + }); + }); }); diff --git a/extensions/sql-database-projects/src/test/testContext.ts b/extensions/sql-database-projects/src/test/testContext.ts index 4759e22b5e..f80bd100c7 100644 --- a/extensions/sql-database-projects/src/test/testContext.ts +++ b/extensions/sql-database-projects/src/test/testContext.ts @@ -31,6 +31,10 @@ export function getDeploymentOptions(): mssql.DeploymentOptions { booleanOptionsDictionary: { 'SampleProperty1': { value: false, description: sampleDesc, displayName: sampleName }, 'SampleProperty2': { value: false, description: sampleDesc, displayName: sampleName } + }, + objectTypesDictionary: { + 'SampleProperty1': sampleName, + 'SampleProperty2': sampleName } }; return defaultOptions; diff --git a/extensions/types/vscode-mssql.d.ts b/extensions/types/vscode-mssql.d.ts index cc6d7598cb..34688cd6af 100644 --- a/extensions/types/vscode-mssql.d.ts +++ b/extensions/types/vscode-mssql.d.ts @@ -499,21 +499,24 @@ declare module 'vscode-mssql' { } /** - * Interface containing deployment options of integer type, value property holds values from \Product\Source\DeploymentApi\ObjectTypes.cs enum + * Interface containing deployment options of string[] type, value property holds enum names (nothing but option name) from \Product\Source\DeploymentApi\ObjectTypes.cs enum */ export interface DacDeployOptionPropertyObject { - value: number[]; + value: string[]; description: string; displayName: string; } - /** - * Interface containing deployment options of integer type, value property holds values from \Product\Source\DeploymentApi\ObjectTypes.cs enum + /* + * Interface containing Deployment options from \Source\DeploymentApi\DacDeployOptions.cs + * These property names should match with the properties defined in \src\Microsoft.SqlTools.ServiceLayer\DacFx\Contracts\DeploymentOptions.cs */ export interface DeploymentOptions { excludeObjectTypes: DacDeployOptionPropertyObject; // key will be the boolean option name booleanOptionsDictionary: { [key: string]: DacDeployOptionPropertyBoolean }; + // key will be the object type enum name(nothing but option name) + objectTypesDictionary: { [key: string]: string }; } /**