From 1ff0a072177ff99faf08f59ee1d1b5c8229f79aa Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Tue, 13 Jun 2023 13:06:12 -1000 Subject: [PATCH] Add support for System database package references for SDK-style sql projects (#23309) * add package reference type for system db references * Add radio buttons * hookup * add test * handle changing reference type radio buttons * cleanup * update strings * change style to type * change more places with style to type * blank lines --- extensions/mssql/src/contracts.ts | 5 ++ extensions/mssql/src/mssql.d.ts | 8 ++- .../src/sqlProjects/sqlProjectsService.ts | 5 +- .../src/common/constants.ts | 5 +- .../src/common/typeHelper.ts | 3 +- .../src/dialogs/addDatabaseReferenceDialog.ts | 72 +++++++++++++++++-- .../dialogs/addDatabaseReferenceQuickpick.ts | 7 +- .../src/models/IDatabaseReferenceSettings.ts | 3 +- .../src/models/project.ts | 7 +- .../src/test/project.test.ts | 38 ++++++++-- .../src/test/projectController.test.ts | 2 +- extensions/types/vscode-mssql.d.ts | 5 ++ 12 files changed, 136 insertions(+), 24 deletions(-) diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts index 1518dfaedb..33567ccbe9 100644 --- a/extensions/mssql/src/contracts.ts +++ b/extensions/mssql/src/contracts.ts @@ -892,6 +892,11 @@ export interface AddSystemDatabaseReferenceParams extends AddDatabaseReferencePa * Type of system database */ systemDatabase: mssql.SystemDatabase; + + /** + * Type of reference - ArtifactReference or PackageReference + */ + referenceType: mssql.SystemDbReferenceType; } export interface AddUserDatabaseReferenceParams extends AddDatabaseReferenceParams { diff --git a/extensions/mssql/src/mssql.d.ts b/extensions/mssql/src/mssql.d.ts index 075972f3f5..9348761785 100644 --- a/extensions/mssql/src/mssql.d.ts +++ b/extensions/mssql/src/mssql.d.ts @@ -348,9 +348,10 @@ declare module 'mssql' { * @param projectUri Absolute path of the project, including .sqlproj * @param systemDatabase Type of system database * @param suppressMissingDependencies Whether to suppress missing dependencies + * @param referenceType Type of reference - ArtifactReference or PackageReference * @param databaseLiteral Literal name used to reference another database in the same server, if not using SQLCMD variables */ - addSystemDatabaseReference(projectUri: string, systemDatabase: SystemDatabase, suppressMissingDependencies: boolean, databaseLiteral?: string): Promise; + addSystemDatabaseReference(projectUri: string, systemDatabase: SystemDatabase, suppressMissingDependencies: boolean, referenceType: SystemDbReferenceType, databaseLiteral?: string): Promise; /** * Add a nuget package database reference to a project @@ -766,6 +767,11 @@ declare module 'mssql' { MSDB = 1 } + export const enum SystemDbReferenceType { + ArtifactReference = 0, + PackageReference = 1 + } + export interface SqlCmdVariable { varName: string; value: string; diff --git a/extensions/mssql/src/sqlProjects/sqlProjectsService.ts b/extensions/mssql/src/sqlProjects/sqlProjectsService.ts index 01a21892aa..1fcba8c4b6 100644 --- a/extensions/mssql/src/sqlProjects/sqlProjectsService.ts +++ b/extensions/mssql/src/sqlProjects/sqlProjectsService.ts @@ -70,10 +70,11 @@ export class SqlProjectsService extends BaseService implements mssql.ISqlProject * @param projectUri Absolute path of the project, including .sqlproj * @param systemDatabase Type of system database * @param suppressMissingDependencies Whether to suppress missing dependencies + * @param SystemDbReferenceType Type of reference - ArtifactReference or PackageReference * @param databaseLiteral Literal name used to reference another database in the same server, if not using SQLCMD variables */ - public async addSystemDatabaseReference(projectUri: string, systemDatabase: mssql.SystemDatabase, suppressMissingDependencies: boolean, databaseLiteral?: string): Promise { - const params: contracts.AddSystemDatabaseReferenceParams = { projectUri: projectUri, systemDatabase: systemDatabase, suppressMissingDependencies: suppressMissingDependencies, databaseLiteral: databaseLiteral }; + public async addSystemDatabaseReference(projectUri: string, systemDatabase: mssql.SystemDatabase, suppressMissingDependencies: boolean, SystemDbReferenceType: mssql.SystemDbReferenceType, databaseLiteral?: string): Promise { + const params: contracts.AddSystemDatabaseReferenceParams = { projectUri: projectUri, systemDatabase: systemDatabase, suppressMissingDependencies: suppressMissingDependencies, referenceType: SystemDbReferenceType, databaseLiteral: databaseLiteral }; return await this.runWithErrorHandling(contracts.AddSystemDatabaseReferenceRequest.type, params); } diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index 28cb67541b..462bc85c27 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -291,7 +291,7 @@ export function retryMessage(name: string, error: string) { return localize('ret //#region Add Database Reference dialog strings export const addDatabaseReferenceDialogName = localize('addDatabaseReferencedialogName', "Add database reference"); export const addDatabaseReferenceOkButtonText = localize('addDatabaseReferenceOkButtonText', "Add reference"); -export const referenceRadioButtonsGroupTitle = localize('referenceRadioButtonsGroupTitle', "Type"); +export const referenceRadioButtonsGroupTitle = localize('referenceRadioButtonsGroupTitle', "Referenced Database Type"); export const projectLabel = localize('projectLocString', "Project"); export const systemDatabase = localize('systemDatabase', "System database"); export const dacpacText = localize('dacpacText', "Data-tier application (.dacpac)"); @@ -323,6 +323,9 @@ export const referencedDatabaseType = localize('referencedDatabaseType', "Refere export const excludeFolderNotSupported = localize('excludeFolderNotSupported', "Excluding folders is not yet supported"); export const unhandledDeleteType = (itemType: string): string => { return localize('unhandledDeleteType', "Unhandled item type during delete: '{0}", itemType); } export const unhandledExcludeType = (itemType: string): string => { return localize('unhandledDeleteType', "Unhandled item type during exclude: '{0}", itemType); } +export const artifactReference = localize('artifactReference', "Artifact Reference"); +export const packageReference = localize('packageReference', "Package Reference"); +export const referenceTypeRadioButtonsGroupTitle = localize('referenceTypeRadioButtonsGroupTitle', "Reference Type"); //#endregion diff --git a/extensions/sql-database-projects/src/common/typeHelper.ts b/extensions/sql-database-projects/src/common/typeHelper.ts index d7ac7f161f..b40651a31c 100644 --- a/extensions/sql-database-projects/src/common/typeHelper.ts +++ b/extensions/sql-database-projects/src/common/typeHelper.ts @@ -9,4 +9,5 @@ import * as vscodeMssql from 'vscode-mssql'; export type ProjectType = mssql.ProjectType | vscodeMssql.ProjectType; export type GetScriptsResult = mssql.GetScriptsResult | vscodeMssql.GetScriptsResult; export type GetFoldersResult = mssql.GetFoldersResult | vscodeMssql.GetFoldersResult; -export type SystemDatabase = mssql.SystemDatabase | vscodeMssql.SystemDatabase; \ No newline at end of file +export type SystemDatabase = mssql.SystemDatabase | vscodeMssql.SystemDatabase; +export type SystemDbReferenceType = mssql.SystemDbReferenceType | vscodeMssql.SystemDbReferenceType; diff --git a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts index 91d21d904d..f294557abf 100644 --- a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts @@ -16,7 +16,7 @@ import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings, IProjectRef import { Deferred } from '../common/promise'; import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry'; import { DbServerValues, ensureSetOrDefined, populateResultWithVars } from './utils'; -import { ProjectType } from 'mssql'; +import { ProjectType, SystemDbReferenceType } from 'mssql'; export enum ReferencedDatabaseType { project, @@ -48,7 +48,10 @@ export class AddDatabaseReferenceDialog { public exampleUsage: azdataType.TextComponent | undefined; private projectRadioButton: azdataType.RadioButtonComponent | undefined; private systemDatabaseRadioButton: azdataType.RadioButtonComponent | undefined; - + private systemDatabaseArtifactRefRadioButton: azdataType.RadioButtonComponent | undefined; + private systemDatabasePackageRefRadioButton: azdataType.RadioButtonComponent | undefined; + private systemDbRefRadioButtonsComponent: azdataType.FormComponent | undefined; + private systemDbRefType: SystemDbReferenceType = SystemDbReferenceType.ArtifactReference; public currentReferencedDatabaseType: ReferencedDatabaseType | undefined; private toDispose: vscode.Disposable[] = []; @@ -107,12 +110,14 @@ export class AddDatabaseReferenceDialog { this.addDatabaseReferenceTab.registerContent(async view => { this.view = view; this.projectFormComponent = await this.createProjectDropdown(); - const radioButtonGroup = this.createRadioButtons(); + const radioButtonGroup = this.createReferenceTypeRadioButtons(); this.systemDatabaseFormComponent = this.createSystemDatabaseDropdown(); this.dacpacFormComponent = this.createDacpacTextbox(); this.nupkgFormComponent = this.createNupkgFormComponentGroup(); const locationDropdown = this.createLocationDropdown(); const variableSection = this.createVariableSection(); + this.systemDbRefRadioButtonsComponent = this.createSystemDbReferenceTypeRadioButtons(); + this.suppressMissingDependenciesErrorsCheckbox = view.modelBuilder.checkBox().withProps({ label: constants.suppressMissingDependenciesErrors }).component(); @@ -148,6 +153,8 @@ export class AddDatabaseReferenceDialog { await this.projectRadioButton?.focus(); } else { await this.systemDatabaseRadioButton?.focus(); + + this.insertSystemDatabaseReferenceTypeComponent(); } this.initDialogComplete.resolve(); @@ -161,7 +168,8 @@ export class AddDatabaseReferenceDialog { const systemDbRef: ISystemDatabaseReferenceSettings = { databaseVariableLiteralValue: this.databaseNameTextbox?.value, systemDb: utils.getSystemDatabase(this.systemDatabaseDropdown?.value), - suppressMissingDependenciesErrors: this.suppressMissingDependenciesErrorsCheckbox?.checked + suppressMissingDependenciesErrors: this.suppressMissingDependenciesErrorsCheckbox?.checked, + systemDbReferenceType: this.systemDbRefType }; referenceSettings = systemDbRef; @@ -212,7 +220,7 @@ export class AddDatabaseReferenceDialog { this.dispose(); } - private createRadioButtons(): azdataType.FormComponent { + private createReferenceTypeRadioButtons(): azdataType.FormComponent { this.projectRadioButton = this.view!.modelBuilder.radioButton() .withProps({ name: 'referencedDatabaseType', @@ -291,10 +299,61 @@ export class AddDatabaseReferenceDialog { }; } + private createSystemDbReferenceTypeRadioButtons(): azdataType.FormComponent { + this.systemDatabasePackageRefRadioButton = this.view!.modelBuilder.radioButton() + .withProps({ + name: 'systemDbRefType', + label: constants.packageReference + }).component(); + + this.systemDatabasePackageRefRadioButton.onDidChangeCheckedState((checked) => { + if (checked) { + this.systemDbRefType = SystemDbReferenceType.PackageReference; + } + }); + + this.systemDatabaseArtifactRefRadioButton = this.view!.modelBuilder.radioButton() + .withProps({ + name: 'systemDbRefType', + label: constants.artifactReference + }).component(); + + this.systemDatabaseArtifactRefRadioButton.onDidChangeCheckedState((checked) => { + if (checked) { + this.systemDbRefType = SystemDbReferenceType.ArtifactReference; + } + }); + + const radioButtons = [this.systemDatabasePackageRefRadioButton!, this.systemDatabaseArtifactRefRadioButton!]; + + const flexRadioButtonsModel: azdataType.FlexContainer = this.view!.modelBuilder.flexContainer() + .withLayout({ flexFlow: 'column' }) + .withItems(radioButtons) + .withProps({ ariaRole: 'radiogroup', ariaLabel: constants.referenceTypeRadioButtonsGroupTitle }) + .component(); + + // default to PackageReference for SDK-style projects + this.systemDatabasePackageRefRadioButton!.checked = true; + + return { + component: flexRadioButtonsModel, + title: constants.referenceTypeRadioButtonsGroupTitle + }; + } + + private insertSystemDatabaseReferenceTypeComponent(): void { + // add the radio buttons to choose ArtifactReference or PackageReference if it's an SDK-syle project + if (this.project.sqlProjStyle === ProjectType.SdkStyle) { + this.formBuilder!.insertFormItem(this.systemDbRefRadioButtonsComponent!, 3); + this.systemDbRefType = SystemDbReferenceType.PackageReference; + } + } + public projectRadioButtonClick(): void { this.formBuilder!.removeFormItem(this.dacpacFormComponent); this.formBuilder!.removeFormItem(this.systemDatabaseFormComponent); this.formBuilder!.removeFormItem(this.nupkgFormComponent); + this.formBuilder!.removeFormItem(this.systemDbRefRadioButtonsComponent); this.formBuilder!.insertFormItem(this.projectFormComponent, 2); this.locationDropdown!.values = constants.locationDropdownValues; @@ -310,6 +369,7 @@ export class AddDatabaseReferenceDialog { this.formBuilder!.removeFormItem(this.projectFormComponent); this.formBuilder!.removeFormItem(this.nupkgFormComponent); this.formBuilder!.insertFormItem(this.systemDatabaseFormComponent, 2); + this.insertSystemDatabaseReferenceTypeComponent(); // update dropdown values because only different database, same server is a valid location for system db references this.locationDropdown!.values = constants.systemDbLocationDropdownValues; @@ -325,6 +385,7 @@ export class AddDatabaseReferenceDialog { this.formBuilder!.removeFormItem(this.systemDatabaseFormComponent); this.formBuilder!.removeFormItem(this.projectFormComponent); this.formBuilder!.removeFormItem(this.nupkgFormComponent); + this.formBuilder!.removeFormItem(this.systemDbRefRadioButtonsComponent); this.formBuilder!.insertFormItem(this.dacpacFormComponent, 2); this.locationDropdown!.values = constants.locationDropdownValues; @@ -339,6 +400,7 @@ export class AddDatabaseReferenceDialog { this.formBuilder!.removeFormItem(this.systemDatabaseFormComponent); this.formBuilder!.removeFormItem(this.projectFormComponent); this.formBuilder!.removeFormItem(this.dacpacFormComponent); + this.formBuilder!.removeFormItem(this.systemDbRefRadioButtonsComponent); this.formBuilder!.insertFormItem(this.nupkgFormComponent, 2); this.locationDropdown!.values = constants.locationDropdownValues; diff --git a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceQuickpick.ts b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceQuickpick.ts index 28a90e1a71..84d974dba6 100644 --- a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceQuickpick.ts +++ b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceQuickpick.ts @@ -13,9 +13,7 @@ import { IDacpacReferenceSettings, INugetPackageReferenceSettings, IProjectRefer import { Project } from '../models/project'; import { getSystemDbOptions, promptDacpacLocation } from './addDatabaseReferenceDialog'; import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry'; -import { ProjectType } from 'mssql'; - - +import { ProjectType, SystemDbReferenceType } from 'vscode-mssql'; /** * Create flow for adding a database reference using only VS Code-native APIs such as QuickPick @@ -137,7 +135,8 @@ async function addSystemDatabaseReference(project: Project): Promisesettings.systemDb as mssql.SystemDatabase; + referenceType = settings.systemDbReferenceType as mssql.SystemDbReferenceType; sqlProjService = this.sqlProjService as mssql.ISqlProjectsService; - result = await sqlProjService.addSystemDatabaseReference(this.projectFilePath, systemDb, settings.suppressMissingDependenciesErrors, settings.databaseVariableLiteralValue); + result = await sqlProjService.addSystemDatabaseReference(this.projectFilePath, systemDb, settings.suppressMissingDependenciesErrors, referenceType, settings.databaseVariableLiteralValue); } else { systemDb = settings.systemDb as vscodeMssql.SystemDatabase; sqlProjService = this.sqlProjService as vscodeMssql.ISqlProjectsService; diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts index 87276f7702..6ead88436e 100644 --- a/extensions/sql-database-projects/src/test/project.test.ts +++ b/extensions/sql-database-projects/src/test/project.test.ts @@ -17,7 +17,7 @@ import { Uri, window } from 'vscode'; import { IDacpacReferenceSettings, INugetPackageReferenceSettings, IProjectReferenceSettings, ISystemDatabaseReferenceSettings } from '../models/IDatabaseReferenceSettings'; import { ItemType } from 'sqldbproj'; import { SystemDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry, DacpacReferenceProjectEntry } from '../models/projectEntry'; -import { ProjectType, SystemDatabase } from 'mssql'; +import { ProjectType, SystemDatabase, SystemDbReferenceType } from 'mssql'; describe('Project: sqlproj content operations', function (): void { before(async function (): Promise { @@ -551,15 +551,40 @@ describe('Project: database references', function (): void { should(ref).equal(undefined, 'msdb reference should be deleted'); }); - it('Should add system database reference correctly', async function (): Promise { + it('Should add system database artifact reference correctly', async function (): Promise { let project = await testUtils.createTestSqlProject(this.test); - const msdbRefSettings: ISystemDatabaseReferenceSettings = { databaseVariableLiteralValue: systemDatabaseToString(SystemDatabase.MSDB), systemDb: SystemDatabase.MSDB, suppressMissingDependenciesErrors: true }; + const msdbRefSettings: ISystemDatabaseReferenceSettings = { + databaseVariableLiteralValue: systemDatabaseToString(SystemDatabase.MSDB), + systemDb: SystemDatabase.MSDB, + suppressMissingDependenciesErrors: true, + systemDbReferenceType: SystemDbReferenceType.ArtifactReference + }; await project.addSystemDatabaseReference(msdbRefSettings); (project.databaseReferences.length).should.equal(1, 'There should be one database reference after adding a reference to msdb'); (project.databaseReferences[0].referenceName).should.equal(msdbRefSettings.databaseVariableLiteralValue, 'databaseName'); (project.databaseReferences[0].suppressMissingDependenciesErrors).should.equal(msdbRefSettings.suppressMissingDependenciesErrors, 'suppressMissingDependenciesErrors'); + const projFileText = (await fs.readFile(project.projectFilePath)).toString(); + (projFileText).should.containEql(''); }); it('Should add a dacpac reference to the same database correctly', async function (): Promise { @@ -783,7 +808,12 @@ describe('Project: database references', function (): void { should(project.databaseReferences.length).equal(0, 'There should be no database references to start with'); - const systemDbReference: ISystemDatabaseReferenceSettings = { databaseVariableLiteralValue: systemDatabaseToString(SystemDatabase.Master), systemDb: SystemDatabase.Master, suppressMissingDependenciesErrors: false }; + const systemDbReference: ISystemDatabaseReferenceSettings = { + databaseVariableLiteralValue: systemDatabaseToString(SystemDatabase.Master), + systemDb: SystemDatabase.Master, + suppressMissingDependenciesErrors: false, + systemDbReferenceType: SystemDbReferenceType.ArtifactReference + }; await project.addSystemDatabaseReference(systemDbReference); project = await Project.openProject(projFilePath); should(project.databaseReferences.length).equal(1, 'There should be one database reference after adding a reference to master'); diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index f640cb1432..16c1ea3d39 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -686,7 +686,7 @@ describe('ProjectsController', function (): void { addDbReferenceDialog.callBase = true; addDbReferenceDialog.setup(x => x.addReferenceClick()).returns(() => { return projController.object.addDatabaseReferenceCallback(proj, - { systemDb: SystemDatabase.Master, databaseName: 'master', suppressMissingDependenciesErrors: false }, + { systemDb: SystemDatabase.Master, databaseName: 'master', suppressMissingDependenciesErrors: false, systemDbReferenceType: mssql.SystemDbReferenceType.ArtifactReference }, { treeDataProvider: new SqlDatabaseProjectTreeViewProvider(), element: undefined }); }); addDbReferenceDialog.setup(x => x.openDialog()).returns(() => Promise.resolve()); diff --git a/extensions/types/vscode-mssql.d.ts b/extensions/types/vscode-mssql.d.ts index d8e30ff642..29268e52c5 100644 --- a/extensions/types/vscode-mssql.d.ts +++ b/extensions/types/vscode-mssql.d.ts @@ -1406,6 +1406,11 @@ declare module 'vscode-mssql' { MSDB = 1 } + export const enum SystemDbReferenceType { + ArtifactReference = 0, + PackageReference = 1 + } + export interface DatabaseReference { suppressMissingDependencies: boolean; databaseVariableLiteralName?: string;