From f4e1f85e0f564f426af88839db9d29290475ea8d Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Tue, 30 Mar 2021 17:06:04 -0700 Subject: [PATCH] Add dacpac references to sqlproj with relative path (#14877) * relative paths written to sqlproj, but can't delete yet * only keep track of relative path * remove leading slash * add test * use path.relative * Add error message if dacpac reference is located on a different drive --- .../src/common/constants.ts | 1 + .../src/controllers/projectController.ts | 5 +- .../src/dialogs/addDatabaseReferenceDialog.ts | 21 +++++++ .../src/models/project.ts | 5 ++ .../src/test/projectController.test.ts | 56 +++++++++++++++++++ 5 files changed, 87 insertions(+), 1 deletion(-) diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index 2434ca5c3e..4c4658e3b4 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -111,6 +111,7 @@ export const databaseNameServerNameVariableRequired = localize('databaseNameServ export const otherServer = 'OtherServer'; export const otherSeverVariable = 'OtherServer'; export const databaseProject = localize('databaseProject', "Database project"); +export const dacpacNotOnSameDrive = (projectLocation: string): string => { return localize('dacpacNotOnSameDrive', "Dacpac references need to be located on the same drive as the project file. The project file is located at {0}", projectLocation); }; // Create Project From Database dialog strings diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index d4ecfa56a8..6dc9e91339 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -580,7 +580,10 @@ export class ProjectsController { } else if ((settings).systemDb !== undefined) { await project.addSystemDatabaseReference(settings); } else { - await project.addDatabaseReference(settings); + // update dacpacFileLocation to relative path to project file + const dacpacRefSettings = settings as IDacpacReferenceSettings; + dacpacRefSettings.dacpacFileLocation = vscode.Uri.file(path.relative(project.projectFolderPath, dacpacRefSettings.dacpacFileLocation.fsPath)); + await project.addDatabaseReference(dacpacRefSettings); } this.refreshProjectsTree(context); diff --git a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts index b0bcc39f41..90b481c993 100644 --- a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts @@ -54,6 +54,27 @@ export class AddDatabaseReferenceDialog { constructor(private project: Project) { this.dialog = azdata.window.createModelViewDialog(constants.addDatabaseReferenceDialogName, 'addDatabaseReferencesDialog'); this.addDatabaseReferenceTab = azdata.window.createTab(constants.addDatabaseReferenceDialogName); + this.dialog.registerCloseValidator(async () => { + return this.validate(); + }); + } + + validate(): boolean { + // only support adding dacpacs that are on the same drive as the sqlproj + if (this.currentReferenceType === ReferenceType.dacpac) { + const projectDrive = path.parse(this.project.projectFilePath).root; + const dacpacDrive = path.parse(this.dacpacTextbox!.value!).root; + + if (projectDrive !== dacpacDrive) { + this.dialog.message = { + text: constants.dacpacNotOnSameDrive(this.project.projectFilePath), + level: azdata.window.MessageLevel.Error + }; + return false; + } + } + + return true; } public async openDialog(): Promise { diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index 1eebc064db..ae815814cf 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -1020,6 +1020,11 @@ export class DacpacReferenceProjectEntry extends FileProjectEntry implements IDa public get databaseName(): string { return path.parse(utils.getPlatformSafeFileEntryPath(this.fsUri.fsPath)).name; } + + public pathForSqlProj(): string { + // need to remove the leading slash from path for build to work + return utils.convertSlashesForSqlProj(this.fsUri.path.substring(1)); + } } export class SystemDatabaseReferenceProjectEntry extends FileProjectEntry implements IDatabaseReferenceProjectEntry { diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index 29e9dff69f..2ed1989873 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -601,6 +601,62 @@ describe('ProjectsController', function (): void { should(showErrorMessageSpy.called).be.true('showErrorMessage should have been called'); }); + it('Should add dacpac references as relative paths', async function (): Promise { + const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline); + const projController = new ProjectsController(); + + const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath); + const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); + const dataWorkspaceMock = TypeMoq.Mock.ofType(); + sinon.stub(vscode.extensions, 'getExtension').returns({ exports: dataWorkspaceMock.object }); + + // add dacpac reference to something in the same folder + should(project1.databaseReferences.length).equal(0, 'There should not be any database references to start with'); + + await projController.addDatabaseReferenceCallback(project1, { + databaseName: this.databaseNameTextbox?.value, + dacpacFileLocation: vscode.Uri.file(path.join(path.dirname(projFilePath), 'sameFolderTest.dacpac')), + suppressMissingDependenciesErrors: false + }, + { treeDataProvider: new SqlDatabaseProjectTreeViewProvider(), element: undefined }); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); + should(project1.databaseReferences.length).equal(1, 'Dacpac reference should have been added'); + should(project1.databaseReferences[0].databaseName).equal('sameFolderTest'); + should(project1.databaseReferences[0].pathForSqlProj()).equal('sameFolderTest.dacpac'); + // make sure reference to sameFolderTest.dacpac was added to project file + let projFileText = (await fs.readFile(projFilePath)).toString(); + should(projFileText).containEql('sameFolderTest.dacpac'); + + // add dacpac reference to something in the a nested folder + await projController.addDatabaseReferenceCallback(project1, { + databaseName: this.databaseNameTextbox?.value, + dacpacFileLocation: vscode.Uri.file(path.join(path.dirname(projFilePath), 'refs', 'nestedFolderTest.dacpac')), + suppressMissingDependenciesErrors: false + }, + { treeDataProvider: new SqlDatabaseProjectTreeViewProvider(), element: undefined }); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); + should(project1.databaseReferences.length).equal(2, 'Another dacpac reference should have been added'); + should(project1.databaseReferences[1].databaseName).equal('nestedFolderTest'); + should(project1.databaseReferences[1].pathForSqlProj()).equal('refs\\nestedFolderTest.dacpac'); + // make sure reference to nestedFolderTest.dacpac was added to project file + projFileText = (await fs.readFile(projFilePath)).toString(); + should(projFileText).containEql('refs\\nestedFolderTest.dacpac'); + + // add dacpac reference to something in the a folder outside of the project + await projController.addDatabaseReferenceCallback(project1, { + databaseName: this.databaseNameTextbox?.value, + dacpacFileLocation: vscode.Uri.file(path.join(path.dirname(projFilePath), '..','someFolder', 'outsideFolderTest.dacpac')), + suppressMissingDependenciesErrors: false + }, + { treeDataProvider: new SqlDatabaseProjectTreeViewProvider(), element: undefined }); + should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); + should(project1.databaseReferences.length).equal(3, 'Another dacpac reference should have been added'); + should(project1.databaseReferences[2].databaseName).equal('outsideFolderTest'); + should(project1.databaseReferences[2].pathForSqlProj()).equal('..\\someFolder\\outsideFolderTest.dacpac'); + // make sure reference to outsideFolderTest.dacpac was added to project file + projFileText = (await fs.readFile(projFilePath)).toString(); + should(projFileText).containEql('..\\someFolder\\outsideFolderTest.dacpac'); + }); }); });