Add project dropdown to Update project from database dialog (#21446)

* Add dropdown populated with projects in current workspace in Update Project from database dialog for target project location

* Select first from the list if no project is preselected

* Address comments
This commit is contained in:
Sakshi Sharma
2023-01-03 09:52:14 -08:00
committed by GitHub
parent 687bd1854c
commit d86044c4e3
4 changed files with 49 additions and 30 deletions

View File

@@ -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) {

View File

@@ -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(<string>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;

View File

@@ -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<void> {
@@ -21,28 +21,28 @@ describe('Update Project From Database Dialog', () => {
sinon.restore();
});
after(async function(): Promise<void> {
after(async function (): Promise<void> {
await testUtils.deleteGeneratedTestFolder();
});
it('Should populate endpoints correctly when no context passed', async function (): Promise<void> {
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<void> {
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((<any>dialog.serverDropdown!.value).displayName, profile.options['connectionName'], `Server dropdown should be "${profile.options['connectionName']}", but instead was "${(<any>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((<any>dialog.serverDropdown!.value).displayName, profile.options['connectionName'], `Server dropdown should be "${profile.options['connectionName']}", but instead was "${(<any>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.');
});
});

View File

@@ -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')
];