diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index f43e040886..9f1aa259e2 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -1563,7 +1563,8 @@ export class ProjectsController { } } catch { } - const updateProjectFromDatabaseDialog = this.getUpdateProjectFromDatabaseDialog(connection, project); + const workspaceProjects = await utils.getSqlProjectsInWorkspace(); + const updateProjectFromDatabaseDialog = this.getUpdateProjectFromDatabaseDialog(connection, project, workspaceProjects); updateProjectFromDatabaseDialog.updateProjectFromDatabaseCallback = async (model) => await this.updateProjectFromDatabaseCallback(model); @@ -1572,8 +1573,8 @@ export class ProjectsController { return updateProjectFromDatabaseDialog; } - public getUpdateProjectFromDatabaseDialog(connection: azdataType.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined, project: Project | undefined): UpdateProjectFromDatabaseDialog { - return new UpdateProjectFromDatabaseDialog(connection, project); + public getUpdateProjectFromDatabaseDialog(connection: azdataType.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined, project: Project | undefined, workspaceProjects: vscode.Uri[]): UpdateProjectFromDatabaseDialog { + return new UpdateProjectFromDatabaseDialog(connection, project, workspaceProjects); } public async updateProjectFromDatabaseCallback(model: UpdateProjectDataModel) { diff --git a/extensions/sql-database-projects/src/dialogs/updateProjectFromDatabaseDialog.ts b/extensions/sql-database-projects/src/dialogs/updateProjectFromDatabaseDialog.ts index 5cd5deeea3..8625a9b594 100644 --- a/extensions/sql-database-projects/src/dialogs/updateProjectFromDatabaseDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/updateProjectFromDatabaseDialog.ts @@ -23,7 +23,7 @@ export class UpdateProjectFromDatabaseDialog { public dialog: azdata.window.Dialog; public serverDropdown: azdata.DropDownComponent | undefined; public databaseDropdown: azdata.DropDownComponent | undefined; - public projectFileTextBox: azdata.InputBoxComponent | undefined; + public projectFileDropdown: azdata.DropDownComponent | undefined; public compareActionRadioButton: azdata.RadioButtonComponent | undefined; private updateProjectFromDatabaseTab: azdata.window.DialogTab; private connectionButton: azdata.ButtonComponent | undefined; @@ -39,7 +39,7 @@ export class UpdateProjectFromDatabaseDialog { public updateProjectFromDatabaseCallback: ((model: UpdateProjectDataModel) => any) | undefined; - constructor(connection: azdata.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined, private project: Project | undefined) { + constructor(connection: azdata.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined, private project: Project | undefined, private workspaceProjects: vscode.Uri[]) { if (connection && 'connectionName' in connection) { this.profile = connection; } @@ -365,17 +365,23 @@ export class UpdateProjectFromDatabaseDialog { private createProjectLocationRow(view: azdata.ModelView): azdata.FlexContainer { const browseFolderButton: azdata.Component = this.createBrowseFileButton(view); - const value = this.project ? this.project.projectFilePath : ''; + let values: string[] = []; + this.workspaceProjects.forEach(projectUri => { + values.push(projectUri.fsPath); + }); - this.projectFileTextBox = view.modelBuilder.inputBox().withProps({ + const value = this.project ? this.project.projectFilePath : (values[0] ?? ''); + + this.projectFileDropdown = view.modelBuilder.dropDown().withProps({ + editable: true, + fireOnTextChange: true, value: value, - ariaLabel: constants.projectLocationLabel, - placeHolder: constants.projectToUpdatePlaceholderText, + values: values, width: cssStyles.updateProjectFromDatabaseTextboxWidth }).component(); - this.projectFileTextBox.onTextChanged(async () => { - await this.projectFileTextBox!.updateProperty('title', this.projectFileTextBox!.value); + this.projectFileDropdown.onValueChanged(async () => { + await this.projectFileDropdown!.updateProperty('title', this.projectFileDropdown!.value); this.tryEnableUpdateButton(); }); @@ -386,7 +392,7 @@ export class UpdateProjectFromDatabaseDialog { }).component(); const projectLocationRow = view.modelBuilder.flexContainer().withItems([projectLocationLabel,], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-bottom': '-5px', 'margin-top': '-7px' } }).component(); - projectLocationRow.addItem(this.projectFileTextBox, { CSSStyles: { 'margin-right': '10px' } }); + projectLocationRow.addItem(this.projectFileDropdown, { CSSStyles: { 'margin-right': '10px' } }); projectLocationRow.addItem(browseFolderButton, { CSSStyles: { 'margin-top': '2px' } }); return projectLocationRow; @@ -416,8 +422,8 @@ export class UpdateProjectFromDatabaseDialog { return; } - this.projectFileTextBox!.value = fileUris[0].fsPath; - await this.projectFileTextBox!.updateProperty('title', fileUris[0].fsPath); + this.projectFileDropdown!.value = fileUris[0].fsPath; + await this.projectFileDropdown!.updateProperty('title', fileUris[0].fsPath); }); return browseFolderButton; @@ -426,7 +432,7 @@ export class UpdateProjectFromDatabaseDialog { private createFolderStructureRow(view: azdata.ModelView): azdata.FlexContainer { this.folderStructureDropDown = view.modelBuilder.dropDown().withProps({ values: [constants.file, constants.flat, constants.objectType, constants.schema, constants.schemaObjectType], - value: constants.schemaObjectType, + value: constants.schemaObjectType, //TODO: Read this value from project info after fixing https://github.com/microsoft/azuredatastudio/issues/20332 ariaLabel: constants.folderStructureLabel, required: true, width: cssStyles.updateProjectFromDatabaseTextboxWidth @@ -493,7 +499,7 @@ export class UpdateProjectFromDatabaseDialog { public tryEnableUpdateButton(): void { if (this.serverDropdown?.value && this.databaseDropdown?.value - && this.projectFileTextBox?.value + && this.projectFileDropdown?.value && this.folderStructureDropDown?.value && this.action !== undefined) { this.dialog.okButton.enabled = true; @@ -547,7 +553,7 @@ export class UpdateProjectFromDatabaseDialog { const targetEndpointInfo: mssql.SchemaCompareEndpointInfo = { endpointType: mssql.SchemaCompareEndpointType.Project, - projectFilePath: this.projectFileTextBox!.value!, + projectFilePath: this.projectFileDropdown!.value! as string, folderStructure: mapExtractTargetEnum(this.folderStructureDropDown!.value), targetScripts: [], dataSchemaProvider: '', @@ -575,14 +581,14 @@ export class UpdateProjectFromDatabaseDialog { return false; } // the selected location should be an existing directory - const parentDirectoryExists = await exists(path.dirname(this.projectFileTextBox!.value!)); + const parentDirectoryExists = await exists(path.dirname(this.projectFileDropdown!.value! as string)); if (!parentDirectoryExists) { - this.showErrorMessage(constants.ProjectParentDirectoryNotExistError(this.projectFileTextBox!.value!)); + this.showErrorMessage(constants.ProjectParentDirectoryNotExistError(this.projectFileDropdown!.value! as string)); return false; } // the selected location must contain a .sqlproj file - const fileExists = await exists(this.projectFileTextBox!.value!); + const fileExists = await exists(this.projectFileDropdown!.value! as string); if (!fileExists) { this.showErrorMessage(constants.noSqlProjFile); return false; diff --git a/extensions/sql-database-projects/src/test/dialogs/updateProjectFromDatabaseDialog.test.ts b/extensions/sql-database-projects/src/test/dialogs/updateProjectFromDatabaseDialog.test.ts index f3d892a471..462af2a062 100644 --- a/extensions/sql-database-projects/src/test/dialogs/updateProjectFromDatabaseDialog.test.ts +++ b/extensions/sql-database-projects/src/test/dialogs/updateProjectFromDatabaseDialog.test.ts @@ -10,7 +10,7 @@ import * as baselines from '../baselines/baselines'; import * as testUtils from '../testUtils'; import { UpdateProjectFromDatabaseDialog } from '../../dialogs/updateProjectFromDatabaseDialog'; -import { mockConnectionProfile } from '../testContext'; +import { mockConnectionProfile, mockURIList } from '../testContext'; describe('Update Project From Database Dialog', () => { before(async function (): Promise { @@ -21,28 +21,28 @@ describe('Update Project From Database Dialog', () => { sinon.restore(); }); - after(async function(): Promise { + after(async function (): Promise { await testUtils.deleteGeneratedTestFolder(); }); it('Should populate endpoints correctly when no context passed', async function (): Promise { - const dialog = new UpdateProjectFromDatabaseDialog(undefined, undefined); + const dialog = new UpdateProjectFromDatabaseDialog(undefined, undefined, []); await dialog.openDialog(); should.equal(dialog.serverDropdown!.value, undefined, `Server dropdown should not be populated, but instead was "${dialog.serverDropdown!.value}".`); should.equal(dialog.databaseDropdown!.value, undefined, `Database dropdown should not be populated, but instead was "${dialog.databaseDropdown!.value}".`); - should.equal(dialog.projectFileTextBox!.value, '', `Project file textbox should not be populated, but instead was "${dialog.projectFileTextBox!.value}".`); + should.equal(dialog.projectFileDropdown!.value, '', `Project file dropdown should not be populated, but instead was "${dialog.projectFileDropdown!.value}".`); should.equal(dialog.dialog.okButton.enabled, false, 'Okay button should be disabled.'); }); it('Should populate endpoints correctly when Project context is passed', async function (): Promise { const project = await testUtils.createTestProject(baselines.openProjectFileBaseline); - const dialog = new UpdateProjectFromDatabaseDialog(undefined, project); + const dialog = new UpdateProjectFromDatabaseDialog(undefined, project, mockURIList); await dialog.openDialog(); should.equal(dialog.serverDropdown!.value, undefined, `Server dropdown should not be populated, but instead was "${dialog.serverDropdown!.value}".`); should.equal(dialog.databaseDropdown!.value, undefined, `Database dropdown should not be populated, but instead was "${dialog.databaseDropdown!.value}".`); - should.equal(dialog.projectFileTextBox!.value, project.projectFilePath, `Project file textbox should be the sqlproj path (${project.projectFilePath}), but instead was "${dialog.projectFileTextBox!.value}".`); + should.equal(dialog.projectFileDropdown!.value, project.projectFilePath, `Project file dropdown should be the sqlproj path (${project.projectFilePath}), but instead was "${dialog.projectFileDropdown!.value}".`); should.equal(dialog.dialog.okButton.enabled, false, 'Okay button should be disabled.'); }); @@ -51,13 +51,13 @@ describe('Update Project From Database Dialog', () => { sinon.stub(azdata.connection, 'listDatabases').resolves([mockConnectionProfile.databaseName!]); const profile = mockConnectionProfile; - const dialog = new UpdateProjectFromDatabaseDialog(profile, undefined); + const dialog = new UpdateProjectFromDatabaseDialog(profile, undefined, []); await dialog.openDialog(); await dialog.populatedInputsPromise; should.equal((dialog.serverDropdown!.value).displayName, profile.options['connectionName'], `Server dropdown should be "${profile.options['connectionName']}", but instead was "${(dialog.serverDropdown!.value).displayName}".`); should.equal(dialog.databaseDropdown!.value, profile.databaseName, `Database dropdown should be "${profile.databaseName}", but instead was "${dialog.databaseDropdown!.value}".`); - should.equal(dialog.projectFileTextBox!.value, '', `Project file textbox should not be populated, but instead was "${dialog.projectFileTextBox!.value}".`); + should.equal(dialog.projectFileDropdown!.value, '', `Project file dropdown should not be populated, but instead was "${dialog.projectFileDropdown!.value}".`); should.equal(dialog.dialog.okButton.enabled, false, 'Okay button should be disabled.'); }); @@ -67,13 +67,19 @@ describe('Update Project From Database Dialog', () => { sinon.stub(azdata.connection, 'listDatabases').resolves([mockConnectionProfile.databaseName!]); const profile = mockConnectionProfile; - const dialog = new UpdateProjectFromDatabaseDialog(profile, project); + const dialog = new UpdateProjectFromDatabaseDialog(profile, project, mockURIList); await dialog.openDialog(); await dialog.populatedInputsPromise; + let uriList: string[] = []; + mockURIList.forEach(projectUri => { + uriList.push(projectUri.fsPath as string); + }); + should.equal((dialog.serverDropdown!.value).displayName, profile.options['connectionName'], `Server dropdown should be "${profile.options['connectionName']}", but instead was "${(dialog.serverDropdown!.value).displayName}".`); should.equal(dialog.databaseDropdown!.value, profile.databaseName, `Database dropdown should as "${profile.databaseName}", but instead was "${dialog.databaseDropdown!.value}".`); - should.equal(dialog.projectFileTextBox!.value, project.projectFilePath, `Project file textbox should be the sqlproj path (${project.projectFilePath}), but instead was "${dialog.projectFileTextBox!.value}".`); + should.equal(dialog.projectFileDropdown!.value, project.projectFilePath, `Project file dropdown should be the sqlproj path (${project.projectFilePath}), but instead was "${dialog.projectFileDropdown!.value}".`); + should.deepEqual(dialog.projectFileDropdown!.values, uriList, `Project file dropdown list should be the sqlproj path (${mockURIList}), but instead was "${dialog.projectFileDropdown!.values}".`); should.equal(dialog.dialog.okButton.enabled, true, 'Okay button should be enabled when dialog is complete.'); }); }); diff --git a/extensions/sql-database-projects/src/test/testContext.ts b/extensions/sql-database-projects/src/test/testContext.ts index 53bd376e30..4fc7d8c8fc 100644 --- a/extensions/sql-database-projects/src/test/testContext.ts +++ b/extensions/sql-database-projects/src/test/testContext.ts @@ -127,3 +127,9 @@ export const mockConnectionProfile: azdata.IConnectionProfile = { connectionName: 'My Connection Name' } }; + +export const mockURIList: vscode.Uri[] = [ + vscode.Uri.file('/test/folder/abc.sqlproj'), + vscode.Uri.file('/test/folder/folder1/abc1.sqlproj'), + vscode.Uri.file('/test/folder/folder2/abc2.sqlproj') +];