Add reference to another sql project (#12186)

* add projects to add database reference dialog

* able to add project references

* check for circular dependency

* only allow adding reference to project in the same workspace

* fix location dropdown when project reference is enabled

* add tests

* more tests

* cleanup

* fix flakey test

* addressing comments
This commit is contained in:
Kim Santiago
2020-09-10 17:44:39 -07:00
committed by GitHub
parent 7df132b307
commit 133ff73a43
11 changed files with 380 additions and 70 deletions

View File

@@ -5,12 +5,14 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as path from 'path';
import * as constants from '../common/constants';
import { Project, SystemDatabase, DatabaseReferenceLocation } from '../models/project';
import { Project, SystemDatabase } from '../models/project';
import { cssStyles } from '../common/uiConstants';
import { IconPathHelper } from '../common/iconHelper';
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings } from '../models/IDatabaseReferenceSettings';
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings, IProjectReferenceSettings } from '../models/IDatabaseReferenceSettings';
import { getSqlProjectFilesInFolder } from '../common/utils';
export enum ReferenceType {
project,
@@ -28,6 +30,8 @@ export class AddDatabaseReferenceDialog {
public addDatabaseReferenceTab: azdata.window.DialogTab;
private view: azdata.ModelView | undefined;
private formBuilder: azdata.FormBuilder | undefined;
private projectDropdown: azdata.DropDownComponent | undefined;
private projectFormComponent: azdata.FormComponent | undefined;
private systemDatabaseDropdown: azdata.DropDownComponent | undefined;
private systemDatabaseFormComponent: azdata.FormComponent | undefined;
public dacpacTextbox: azdata.InputBoxComponent | undefined;
@@ -41,23 +45,16 @@ export class AddDatabaseReferenceDialog {
public exampleUsage: azdata.TextComponent | undefined;
public currentReferenceType: ReferenceType | undefined;
private referenceLocationMap: Map<string, DatabaseReferenceLocation>;
private toDispose: vscode.Disposable[] = [];
private initDialogComplete: Deferred<void> | undefined;
private initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
public addReference: ((proj: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings) => any) | undefined;
public addReference: ((proj: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings | IProjectReferenceSettings) => any) | undefined;
constructor(private project: Project) {
this.dialog = azdata.window.createModelViewDialog(constants.addDatabaseReferenceDialogName);
this.addDatabaseReferenceTab = azdata.window.createTab(constants.addDatabaseReferenceDialogName);
this.referenceLocationMap = new Map([
[constants.sameDatabase, DatabaseReferenceLocation.sameDatabase],
[constants.differentDbSameServer, DatabaseReferenceLocation.differentDatabaseSameServer],
[constants.differentDbDifferentServer, DatabaseReferenceLocation.differentDatabaseDifferentServer]
]);
}
public async openDialog(): Promise<void> {
@@ -84,6 +81,7 @@ export class AddDatabaseReferenceDialog {
private initializeTab(): void {
this.addDatabaseReferenceTab.registerContent(async view => {
this.view = view;
this.projectFormComponent = await this.createProjectDropdown();
const radioButtonGroup = this.createRadioButtons();
this.systemDatabaseFormComponent = this.createSystemDatabaseDropdown();
this.dacpacFormComponent = this.createDacpacTextbox();
@@ -100,7 +98,7 @@ export class AddDatabaseReferenceDialog {
title: '',
components: [
radioButtonGroup,
this.systemDatabaseFormComponent,
this.currentReferenceType === ReferenceType.project ? this.projectFormComponent : this.systemDatabaseFormComponent,
locationDropdown,
variableSection,
exampleUsage,
@@ -118,15 +116,27 @@ export class AddDatabaseReferenceDialog {
let formModel = this.formBuilder.component();
await view.initializeModel(formModel);
this.updateEnabledInputBoxes();
this.initDialogComplete?.resolve();
});
}
public async addReferenceClick(): Promise<void> {
let referenceSettings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings;
let referenceSettings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings | IProjectReferenceSettings;
if (this.currentReferenceType === ReferenceType.systemDb) {
if (this.currentReferenceType === ReferenceType.project) {
referenceSettings = {
projectName: <string>this.projectDropdown?.value,
projectGuid: '',
projectRelativePath: undefined,
databaseName: <string>this.databaseNameTextbox?.value,
databaseVariable: <string>this.databaseVariableTextbox?.value,
serverName: <string>this.serverNameTextbox?.value,
serverVariable: <string>this.serverVariableTextbox?.value,
suppressMissingDependenciesErrors: <boolean>this.suppressMissingDependenciesErrorsCheckbox?.checked
};
} else if (this.currentReferenceType === ReferenceType.systemDb) {
referenceSettings = {
databaseName: <string>this.databaseNameTextbox?.value,
systemDb: <string>this.systemDatabaseDropdown?.value === constants.master ? SystemDatabase.master : SystemDatabase.msdb,
@@ -135,14 +145,12 @@ export class AddDatabaseReferenceDialog {
} else { // this.currentReferenceType === ReferenceType.dacpac
referenceSettings = {
databaseName: <string>this.databaseNameTextbox?.value,
databaseLocation: <DatabaseReferenceLocation>this.referenceLocationMap.get(<string>this.locationDropdown?.value),
dacpacFileLocation: vscode.Uri.file(<string>this.dacpacTextbox?.value),
databaseVariable: <string>this.databaseVariableTextbox?.value,
serverName: <string>this.serverNameTextbox?.value,
serverVariable: <string>this.serverVariableTextbox?.value,
suppressMissingDependenciesErrors: <boolean>this.suppressMissingDependenciesErrorsCheckbox?.checked
};
// TODO: add project reference support
}
await this.addReference!(this.project, referenceSettings);
@@ -151,14 +159,22 @@ export class AddDatabaseReferenceDialog {
}
private createRadioButtons(): azdata.FormComponent {
// TODO: add project reference button
const projectRadioButton = this.view!.modelBuilder.radioButton()
.withProperties({
name: 'referenceType',
label: constants.projectRadioButtonTitle
}).component();
projectRadioButton.onDidClick(() => {
this.projectRadioButtonClick();
});
const systemDatabaseRadioButton = this.view!.modelBuilder.radioButton()
.withProperties({
name: 'referenceType',
label: constants.systemDatabaseRadioButtonTitle
}).component();
systemDatabaseRadioButton.checked = true;
systemDatabaseRadioButton.onDidClick(() => {
this.systemDbRadioButtonClick();
});
@@ -173,10 +189,20 @@ export class AddDatabaseReferenceDialog {
this.dacpacRadioButtonClick();
});
this.currentReferenceType = ReferenceType.systemDb;
if (this.projectDropdown?.values?.length) {
projectRadioButton.checked = true;
this.currentReferenceType = ReferenceType.project;
} else {
systemDatabaseRadioButton.checked = true;
this.currentReferenceType = ReferenceType.systemDb;
// disable projects radio button if there aren't any projects that can be added as a reference
projectRadioButton.enabled = false;
}
let flexRadioButtonsModel: azdata.FlexContainer = this.view!.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.withItems([systemDatabaseRadioButton, dacpacRadioButton])
.withItems([projectRadioButton, systemDatabaseRadioButton, dacpacRadioButton])
.withProperties({ ariaRole: 'radiogroup' })
.component();
@@ -186,21 +212,35 @@ export class AddDatabaseReferenceDialog {
};
}
public projectRadioButtonClick(): void {
this.formBuilder!.removeFormItem(<azdata.FormComponent>this.dacpacFormComponent);
this.formBuilder!.removeFormItem(<azdata.FormComponent>this.systemDatabaseFormComponent);
this.formBuilder!.insertFormItem(<azdata.FormComponent>this.projectFormComponent, 2);
this.currentReferenceType = ReferenceType.project;
this.updateEnabledInputBoxes();
this.tryEnableAddReferenceButton();
this.updateExampleUsage();
}
public systemDbRadioButtonClick(): void {
this.formBuilder!.removeFormItem(<azdata.FormComponent>this.dacpacFormComponent);
this.formBuilder!.removeFormItem(<azdata.FormComponent>this.projectFormComponent);
this.formBuilder!.insertFormItem(<azdata.FormComponent>this.systemDatabaseFormComponent, 2);
// update dropdown values because only different database, same server is a valid location for system db references
this.locationDropdown!.values = constants.systemDbLocationDropdownValues;
this.locationDropdown!.value = constants.differentDbSameServer;
this.currentReferenceType = ReferenceType.systemDb;
this.updateEnabledInputBoxes(true);
this.updateEnabledInputBoxes();
this.tryEnableAddReferenceButton();
this.updateExampleUsage();
}
public dacpacRadioButtonClick(): void {
this.formBuilder!.removeFormItem(<azdata.FormComponent>this.systemDatabaseFormComponent);
this.formBuilder!.removeFormItem(<azdata.FormComponent>this.projectFormComponent);
this.formBuilder!.insertFormItem(<azdata.FormComponent>this.dacpacFormComponent, 2);
this.locationDropdown!.values = constants.locationDropdownValues;
@@ -212,6 +252,39 @@ export class AddDatabaseReferenceDialog {
this.updateExampleUsage();
}
private async createProjectDropdown(): Promise<azdata.FormComponent> {
this.projectDropdown = this.view!.modelBuilder.dropDown().withProperties({
ariaLabel: constants.databaseProject
}).component();
// get projects in workspace
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders?.length) {
let projectFiles = await getSqlProjectFilesInFolder(workspaceFolders[0].uri.fsPath);
// check if current project is in same open folder (should only be able to add a reference to another project in
// the folder if the current project is also in the folder)
if (projectFiles.find(p => p === this.project.projectFilePath)) {
// filter out current project
projectFiles = projectFiles.filter(p => p !== this.project.projectFilePath);
projectFiles.forEach(p => {
projectFiles[projectFiles.indexOf(p)] = path.parse(p).name;
});
this.projectDropdown.values = projectFiles;
} else {
this.projectDropdown.values = [];
}
}
return {
component: this.projectDropdown,
title: constants.databaseProject
};
}
private createSystemDatabaseDropdown(): azdata.FormComponent {
this.systemDatabaseDropdown = this.view!.modelBuilder.dropDown().withProperties({
values: [constants.master, constants.msdb],
@@ -286,9 +359,11 @@ export class AddDatabaseReferenceDialog {
private createLocationDropdown(): azdata.FormComponent {
this.locationDropdown = this.view!.modelBuilder.dropDown().withProperties({
ariaLabel: constants.locationDropdown,
values: constants.systemDbLocationDropdownValues
values: this.currentReferenceType === ReferenceType.systemDb ? constants.systemDbLocationDropdownValues : constants.locationDropdownValues
}).component();
this.locationDropdown.value = constants.differentDbSameServer;
this.locationDropdown.onValueChanged(() => {
this.updateEnabledInputBoxes();
this.tryEnableAddReferenceButton();
@@ -305,7 +380,9 @@ export class AddDatabaseReferenceDialog {
* Update the enabled input boxes based on what the location of the database reference selected in the dropdown is
* @param isSystemDb
*/
public updateEnabledInputBoxes(isSystemDb: boolean = false): void {
public updateEnabledInputBoxes(): void {
const isSystemDb = this.currentReferenceType === ReferenceType.systemDb;
if (this.locationDropdown?.value === constants.sameDatabase) {
this.databaseNameTextbox!.enabled = false;
this.databaseVariableTextbox!.enabled = false;
@@ -387,7 +464,7 @@ export class AddDatabaseReferenceDialog {
private createExampleUsage(): azdata.FormComponent {
this.exampleUsage = this.view!.modelBuilder.text().withProperties({
value: constants.systemDatabaseReferenceRequired,
value: this.currentReferenceType === ReferenceType.project ? constants.databaseNameRequiredVariableOptional : constants.systemDatabaseReferenceRequired,
CSSStyles: { 'user-select': 'text' }
}).component();
@@ -440,27 +517,35 @@ export class AddDatabaseReferenceDialog {
*/
public tryEnableAddReferenceButton(): void {
switch (this.currentReferenceType) {
case ReferenceType.project: {
this.dialog.okButton.enabled = this.projectRequiredFieldsFilled();
break;
}
case ReferenceType.systemDb: {
this.dialog.okButton.enabled = !!this.databaseNameTextbox?.value;
break;
}
case ReferenceType.dacpac: {
this.dialog.okButton.enabled = this.dacpacFieldsRequiredFieldsFilled();
this.dialog.okButton.enabled = this.dacpacRequiredFieldsFilled();
break;
}
case ReferenceType.project: {
// TODO
}
}
}
private dacpacFieldsRequiredFieldsFilled(): boolean {
private dacpacRequiredFieldsFilled(): boolean {
return !!this.dacpacTextbox?.value &&
((this.locationDropdown?.value === constants.sameDatabase)
|| (this.locationDropdown?.value === constants.differentDbSameServer && this.differentDatabaseSameServerRequiredFieldsFilled())
|| ((this.locationDropdown?.value === constants.differentDbDifferentServer && this.differentDatabaseDifferentServerRequiredFieldsFilled())));
}
private projectRequiredFieldsFilled(): boolean {
return !!this.projectDropdown?.value &&
((this.locationDropdown?.value === constants.sameDatabase)
|| (this.locationDropdown?.value === constants.differentDbSameServer && this.differentDatabaseSameServerRequiredFieldsFilled())
|| ((this.locationDropdown?.value === constants.differentDbDifferentServer && this.differentDatabaseDifferentServerRequiredFieldsFilled())));
}
private differentDatabaseSameServerRequiredFieldsFilled(): boolean {
return !!this.databaseNameTextbox?.value;
}