Apply changes from remote database to sqlproj - schema-compare changes (#17679)

* update project from database

* update project from database

* Merge from main

* Removing dupe test stub

* PR feedback

* cleanup

* PR feedback

* Fixing tests, adding stubs to update sqlproj as schema compare target

* updating code comment

Co-authored-by: Noureldine Yehia <t-nyehia@microsoft.com>
This commit is contained in:
Benjin Dubishar
2021-11-22 10:50:36 -08:00
committed by GitHub
parent 0caa6390b9
commit 1f98f29eae
7 changed files with 507 additions and 131 deletions

View File

@@ -9,7 +9,7 @@ import * as loc from '../localizedConstants';
import * as path from 'path';
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
import { TelemetryReporter, TelemetryViews } from '../telemetry';
import { getEndpointName, getRootPath, exists } from '../utils';
import { getEndpointName, getRootPath, exists, getAzdataApi, getSchemaCompareEndpointString } from '../utils';
import * as mssql from '../../../mssql';
const titleFontSize: number = 13;
@@ -18,13 +18,16 @@ interface Deferred<T> {
resolve: (result: T | Promise<T>) => void;
reject: (reason: any) => void;
}
export class SchemaCompareDialog {
public dialog: azdata.window.Dialog;
public dialogName: string;
private schemaCompareTab: azdata.window.DialogTab;
private sourceDacpacRadioButton: azdata.RadioButtonComponent;
private sourceDatabaseRadioButton: azdata.RadioButtonComponent;
private schemaCompareTab: azdata.window.DialogTab;
private sourceProjectRadioButton: azdata.RadioButtonComponent;
private sourceDacpacComponent: azdata.FormComponent;
private sourceProjectFilePathComponent: azdata.FormComponent;
private sourceTextBox: azdata.InputBoxComponent;
private sourceFileButton: azdata.ButtonComponent;
private sourceServerComponent: azdata.FormComponent;
@@ -32,22 +35,30 @@ export class SchemaCompareDialog {
private sourceConnectionButton: azdata.ButtonComponent;
private sourceDatabaseComponent: azdata.FormComponent;
private sourceDatabaseDropdown: azdata.DropDownComponent;
private sourceEndpointType: mssql.SchemaCompareEndpointType;
private sourceDbEditable: string;
private sourceDacpacPath: string;
private sourceProjectFilePath: string;
private targetDacpacComponent: azdata.FormComponent;
private targetProjectFilePathComponent: azdata.FormComponent;
private targetProjectStructureComponent: azdata.FormComponent;
private targetTextBox: azdata.InputBoxComponent;
private targetFileButton: azdata.ButtonComponent;
private targetStructureDropdown: azdata.DropDownComponent;
private targetServerComponent: azdata.FormComponent;
protected targetServerDropdown: azdata.DropDownComponent;
private targetConnectionButton: azdata.ButtonComponent;
private targetDatabaseComponent: azdata.FormComponent;
private targetDatabaseDropdown: azdata.DropDownComponent;
private formBuilder: azdata.FormBuilder;
private sourceIsDacpac: boolean;
private targetIsDacpac: boolean;
private connectionId: string;
private sourceDbEditable: string;
private targetDacpacPath: string;
private targetProjectFilePath: string;
private targetEndpointType: mssql.SchemaCompareEndpointType;
private targetDbEditable: string;
private previousSource: mssql.SchemaCompareEndpointInfo;
private previousTarget: mssql.SchemaCompareEndpointInfo;
private formBuilder: azdata.FormBuilder;
private connectionId: string;
private toDispose: vscode.Disposable[] = [];
private initDialogComplete: Deferred<void>;
private initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
@@ -59,6 +70,11 @@ export class SchemaCompareDialog {
constructor(private schemaCompareMainWindow: SchemaCompareMainWindow, private view?: azdata.ModelView, private extensionContext?: vscode.ExtensionContext) {
this.previousSource = schemaCompareMainWindow.sourceEndpointInfo;
this.previousTarget = schemaCompareMainWindow.targetEndpointInfo;
this.dialog = azdata.window.createModelViewDialog(loc.SchemaCompareLabel);
this.dialog.registerCloseValidator(async () => {
return this.validate();
});
}
protected async initializeDialog(): Promise<void> {
@@ -79,31 +95,17 @@ export class SchemaCompareDialog {
this.dialog.okButton.label = loc.OkButtonText;
this.dialog.okButton.enabled = false;
this.dialog.okButton.onClick(async () => await this.execute());
this.toDispose.push(this.dialog.okButton.onClick(async () => await this.handleOkButtonClick()));
this.dialog.cancelButton.label = loc.CancelButtonText;
this.dialog.cancelButton.onClick(async () => await this.cancel());
this.toDispose.push(this.dialog.cancelButton.onClick(async () => await this.cancel()));
azdata.window.openDialog(this.dialog);
await this.initDialogPromise;
}
public async execute(): Promise<void> {
if (this.sourceIsDacpac) {
this.schemaCompareMainWindow.sourceEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '',
databaseName: '',
ownerUri: '',
packageFilePath: this.sourceTextBox.value,
connectionDetails: undefined,
projectFilePath: '',
folderStructure: '',
targetScripts: [],
dataSchemaProvider: ''
};
} else {
if (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Database) {
const sourceServerDropdownValue = this.sourceServerDropdown.value as ConnectionDropdownValue;
const ownerUri = await azdata.connection.getUriForConnection(sourceServerDropdownValue.connection.connectionId);
@@ -113,31 +115,45 @@ export class SchemaCompareDialog {
serverName: sourceServerDropdownValue.name,
databaseName: this.sourceDatabaseDropdown.value.toString(),
ownerUri: ownerUri,
packageFilePath: '',
connectionDetails: undefined,
connectionName: sourceServerDropdownValue.connection.options.connectionName,
projectFilePath: '',
folderStructure: '',
targetScripts: [],
dataSchemaProvider: ''
folderStructure: '',
packageFilePath: '',
dataSchemaProvider: '',
connectionDetails: undefined,
connectionName: sourceServerDropdownValue.connection.options.connectionName
};
}
if (this.targetIsDacpac) {
this.schemaCompareMainWindow.targetEndpointInfo = {
} else if (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Dacpac) {
this.schemaCompareMainWindow.sourceEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '',
databaseName: '',
ownerUri: '',
packageFilePath: this.targetTextBox.value,
connectionDetails: undefined,
projectFilePath: '',
folderStructure: '',
targetScripts: [],
dataSchemaProvider: ''
folderStructure: '',
dataSchemaProvider: '',
packageFilePath: this.sourceTextBox.value,
connectionDetails: undefined
};
} else {
this.schemaCompareMainWindow.sourceEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Project,
projectFilePath: this.sourceTextBox.value,
targetScripts: await this.getTargetScripts(true),
dataSchemaProvider: await this.getDsp(this.sourceTextBox.value),
folderStructure: '',
serverDisplayName: '',
serverName: '',
databaseName: '',
ownerUri: '',
packageFilePath: '',
connectionDetails: undefined
};
}
if (this.targetEndpointType === mssql.SchemaCompareEndpointType.Database) {
const targetServerDropdownValue = this.targetServerDropdown.value as ConnectionDropdownValue;
const ownerUri = await azdata.connection.getUriForConnection(targetServerDropdownValue.connection.connectionId);
@@ -147,20 +163,48 @@ export class SchemaCompareDialog {
serverName: targetServerDropdownValue.name,
databaseName: this.targetDatabaseDropdown.value.toString(),
ownerUri: ownerUri,
packageFilePath: '',
connectionDetails: undefined,
connectionName: targetServerDropdownValue.connection.options.connectionName,
projectFilePath: '',
folderStructure: '',
targetScripts: [],
dataSchemaProvider: ''
packageFilePath: '',
dataSchemaProvider: '',
connectionDetails: undefined,
connectionName: targetServerDropdownValue.connection.options.connectionName
};
} else if (this.targetEndpointType === mssql.SchemaCompareEndpointType.Dacpac) {
this.schemaCompareMainWindow.targetEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '',
databaseName: '',
ownerUri: '',
projectFilePath: '',
folderStructure: '',
targetScripts: [],
dataSchemaProvider: '',
packageFilePath: this.targetTextBox.value,
connectionDetails: undefined
};
} else {
this.schemaCompareMainWindow.targetEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Project,
projectFilePath: this.targetTextBox.value,
folderStructure: this.targetStructureDropdown!.value as string,
targetScripts: await this.getTargetScripts(false),
dataSchemaProvider: await this.getDsp(this.targetTextBox.value),
serverDisplayName: '',
serverName: '',
databaseName: '',
ownerUri: '',
packageFilePath: '',
connectionDetails: undefined
};
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareDialog, 'SchemaCompareStart')
.withAdditionalProperties({
sourceIsDacpac: this.sourceIsDacpac.toString(),
targetIsDacpac: this.targetIsDacpac.toString()
sourceEndpointType: getSchemaCompareEndpointString(this.sourceEndpointType),
targetEndpointType: getSchemaCompareEndpointString(this.targetEndpointType)
}).send();
// update source and target values that are displayed
@@ -198,6 +242,7 @@ export class SchemaCompareDialog {
}
protected async cancel(): Promise<void> {
this.dispose();
}
private async initializeSchemaCompareTab(): Promise<void> {
@@ -206,36 +251,67 @@ export class SchemaCompareDialog {
this.view = view;
}
let sourceValue = '';
if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Dacpac) {
sourceValue = this.schemaCompareMainWindow.sourceEndpointInfo.packageFilePath;
} else if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Project) {
sourceValue = this.schemaCompareMainWindow.sourceEndpointInfo.projectFilePath;
}
this.sourceTextBox = this.view.modelBuilder.inputBox().withProps({
value: this.schemaCompareMainWindow.sourceEndpointInfo ? this.schemaCompareMainWindow.sourceEndpointInfo.packageFilePath : '',
value: sourceValue,
width: this.textBoxWidth,
ariaLabel: loc.sourceFile
}).component();
this.sourceTextBox.onTextChanged(async (e) => {
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
if (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Dacpac) {
this.sourceDacpacPath = e;
} else if (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Project) {
this.sourceProjectFilePath = e;
}
});
let targetValue = '';
if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Dacpac) {
targetValue = this.schemaCompareMainWindow.targetEndpointInfo.packageFilePath;
} else if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Project) {
targetValue = this.schemaCompareMainWindow.targetEndpointInfo.projectFilePath;
}
this.targetTextBox = this.view.modelBuilder.inputBox().withProps({
value: this.schemaCompareMainWindow.targetEndpointInfo ? this.schemaCompareMainWindow.targetEndpointInfo.packageFilePath : '',
value: targetValue,
width: this.textBoxWidth,
ariaLabel: loc.targetFile
}).component();
this.targetTextBox.onTextChanged(async () => {
this.targetTextBox.onTextChanged(async (e) => {
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
if (this.targetEndpointType === mssql.SchemaCompareEndpointType.Dacpac) {
this.targetDacpacPath = e;
} else if (this.targetEndpointType === mssql.SchemaCompareEndpointType.Project) {
this.targetProjectFilePath = e;
}
});
this.sourceServerComponent = this.createSourceServerDropdown();
this.sourceDatabaseComponent = this.createSourceDatabaseDropdown();
this.targetServerComponent = this.createTargetServerDropdown();
this.targetDatabaseComponent = this.createTargetDatabaseDropdown();
this.sourceDacpacComponent = this.createFileBrowser(false, this.schemaCompareMainWindow.sourceEndpointInfo);
this.targetDacpacComponent = this.createFileBrowser(true, this.schemaCompareMainWindow.targetEndpointInfo);
this.sourceDacpacComponent = this.createFileBrowser(false, true, this.schemaCompareMainWindow.sourceEndpointInfo);
this.targetDacpacComponent = this.createFileBrowser(true, true, this.schemaCompareMainWindow.targetEndpointInfo);
this.sourceProjectFilePathComponent = this.createFileBrowser(false, false, this.schemaCompareMainWindow.sourceEndpointInfo);
this.targetProjectFilePathComponent = this.createFileBrowser(true, false, this.schemaCompareMainWindow.targetEndpointInfo);
this.targetProjectStructureComponent = this.createStructureDropdown();
let sourceRadioButtons = this.createSourceRadioButtons();
let targetRadioButtons = this.createTargetRadioButtons();
@@ -243,18 +319,27 @@ export class SchemaCompareDialog {
let sourceComponents = [];
let targetComponents = [];
// start source and target with either dacpac or database selection based on what the previous value was
// start source and target with either dacpac, database, or project selection based on what the previous value was
if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) {
sourceComponents = [
sourceRadioButtons,
this.sourceServerComponent,
this.sourceDatabaseComponent
];
} else {
} else if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Dacpac) {
sourceComponents = [
sourceRadioButtons,
this.sourceDacpacComponent,
];
} else if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Project) {
sourceComponents = [
sourceRadioButtons,
this.sourceProjectFilePathComponent,
];
} else {
sourceComponents = [
sourceRadioButtons,
];
}
if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) {
@@ -263,11 +348,21 @@ export class SchemaCompareDialog {
this.targetServerComponent,
this.targetDatabaseComponent
];
} else {
} else if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Dacpac) {
targetComponents = [
targetRadioButtons,
this.targetDacpacComponent,
];
} else if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Project) {
targetComponents = [
targetRadioButtons,
this.targetProjectFilePathComponent,
this.targetProjectStructureComponent
];
} else {
targetComponents = [
targetRadioButtons,
];
}
this.formBuilder = <azdata.FormBuilder>this.view.modelBuilder.formContainer()
@@ -290,17 +385,26 @@ export class SchemaCompareDialog {
let formModel = this.formBuilder.component();
await this.view.initializeModel(formModel);
if (this.sourceIsDacpac) {
await this.sourceDacpacRadioButton.focus();
} else {
await this.sourceDatabaseRadioButton.focus();
switch (this.sourceEndpointType) {
case (mssql.SchemaCompareEndpointType.Database):
await this.sourceDatabaseRadioButton.focus();
break;
case (mssql.SchemaCompareEndpointType.Dacpac):
await this.sourceDacpacRadioButton.focus();
break;
case (mssql.SchemaCompareEndpointType.Project):
await this.sourceProjectRadioButton.focus();
break;
}
this.initDialogComplete.resolve();
});
}
private createFileBrowser(isTarget: boolean, endpoint: mssql.SchemaCompareEndpointInfo): azdata.FormComponent {
private createFileBrowser(isTarget: boolean, dacpac: boolean, endpoint: mssql.SchemaCompareEndpointInfo): azdata.FormComponent {
let currentTextbox = isTarget ? this.targetTextBox : this.sourceTextBox;
if (isTarget) {
this.targetFileButton = this.view.modelBuilder.button().withProps({
title: loc.selectTargetFile,
@@ -318,8 +422,9 @@ export class SchemaCompareDialog {
}
let currentButton = isTarget ? this.targetFileButton : this.sourceFileButton;
const filter = dacpac ? 'dacpac' : 'sqlproj';
currentButton.onDidClick(async (click) => {
currentButton.onDidClick(async () => {
// file browser should open where the current dacpac is or the appropriate default folder
let rootPath = getRootPath();
let defaultUri = endpoint && endpoint.packageFilePath && await exists(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath;
@@ -332,7 +437,7 @@ export class SchemaCompareDialog {
defaultUri: vscode.Uri.file(defaultUri),
openLabel: loc.open,
filters: {
'dacpac Files': ['dacpac'],
'Files': [filter],
}
}
);
@@ -352,6 +457,22 @@ export class SchemaCompareDialog {
};
}
private createStructureDropdown(): azdata.FormComponent {
this.targetStructureDropdown = this.view.modelBuilder.dropDown().withProps({
editable: true,
fireOnTextChange: true,
ariaLabel: loc.targetStructure,
width: this.textBoxWidth,
values: [loc.file, loc.flat, loc.objectType, loc.schema, loc.schemaObjectType],
value: loc.schemaObjectType,
}).component();
return {
component: this.targetStructureDropdown,
title: loc.StructureDropdownLabel,
};
}
private createSourceRadioButtons(): azdata.FormComponent {
this.sourceDacpacRadioButton = this.view.modelBuilder.radioButton()
.withProps({
@@ -365,36 +486,66 @@ export class SchemaCompareDialog {
label: loc.DatabaseRadioButtonLabel
}).component();
this.sourceProjectRadioButton = this.view.modelBuilder.radioButton()
.withProps({
name: 'source',
label: loc.ProjectRadioButtonLabel
}).component();
// show dacpac file browser
this.sourceDacpacRadioButton.onDidClick(async () => {
this.sourceIsDacpac = true;
this.sourceEndpointType = mssql.SchemaCompareEndpointType.Dacpac;
this.sourceTextBox.value = this.sourceDacpacPath;
this.formBuilder.removeFormItem(this.sourceServerComponent);
this.formBuilder.removeFormItem(this.sourceDatabaseComponent);
this.formBuilder.removeFormItem(this.sourceProjectFilePathComponent);
this.formBuilder.insertFormItem(this.sourceDacpacComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// show server and db dropdowns
this.sourceDatabaseRadioButton.onDidClick(async () => {
this.sourceIsDacpac = false;
this.sourceEndpointType = mssql.SchemaCompareEndpointType.Database;
this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.insertFormItem(this.sourceDatabaseComponent, 3, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.removeFormItem(this.sourceDacpacComponent);
this.formBuilder.removeFormItem(this.sourceProjectFilePathComponent);
await this.populateServerDropdown(false);
});
// show project directory browser
this.sourceProjectRadioButton.onDidClick(async () => {
this.sourceEndpointType = mssql.SchemaCompareEndpointType.Project;
this.sourceTextBox.value = this.sourceProjectFilePath;
this.formBuilder.removeFormItem(this.sourceServerComponent);
this.formBuilder.removeFormItem(this.sourceDatabaseComponent);
this.formBuilder.removeFormItem(this.sourceDacpacComponent);
this.formBuilder.insertFormItem(this.sourceProjectFilePathComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// if source is currently a db, show it in the server and db dropdowns
if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) {
this.sourceDatabaseRadioButton.checked = true;
this.sourceIsDacpac = false;
} else {
this.sourceEndpointType = mssql.SchemaCompareEndpointType.Database;
} else if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Dacpac) {
this.sourceDacpacRadioButton.checked = true;
this.sourceIsDacpac = true;
this.sourceEndpointType = mssql.SchemaCompareEndpointType.Dacpac;
} else if (this.schemaCompareMainWindow.sourceEndpointInfo) {
this.sourceProjectRadioButton.checked = true;
this.sourceEndpointType = mssql.SchemaCompareEndpointType.Project;
}
let radioButtons = [this.sourceDatabaseRadioButton, this.sourceDacpacRadioButton];
if (vscode.extensions.getExtension(loc.sqlDatabaseProjectExtensionId)) {
radioButtons.push(this.sourceProjectRadioButton);
}
let flexRadioButtonsModel = this.view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.withItems([this.sourceDacpacRadioButton, this.sourceDatabaseRadioButton])
.withItems(radioButtons)
.withProps({ ariaRole: 'radiogroup' })
.component();
@@ -417,38 +568,69 @@ export class SchemaCompareDialog {
label: loc.DatabaseRadioButtonLabel
}).component();
let projectRadioButton = this.view.modelBuilder.radioButton()
.withProps({
name: 'target',
label: loc.ProjectRadioButtonLabel
}).component();
// show dacpac file browser
dacpacRadioButton.onDidClick(async () => {
this.targetIsDacpac = true;
this.targetEndpointType = mssql.SchemaCompareEndpointType.Dacpac;
this.targetTextBox.value = this.targetDacpacPath;
this.formBuilder.removeFormItem(this.targetServerComponent);
this.formBuilder.removeFormItem(this.targetDatabaseComponent);
this.formBuilder.removeFormItem(this.targetProjectFilePathComponent);
this.formBuilder.removeFormItem(this.targetProjectStructureComponent);
this.formBuilder.addFormItem(this.targetDacpacComponent, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// show server and db dropdowns
databaseRadioButton.onDidClick(async () => {
this.targetIsDacpac = false;
this.targetEndpointType = mssql.SchemaCompareEndpointType.Database;
this.formBuilder.removeFormItem(this.targetDacpacComponent);
this.formBuilder.removeFormItem(this.targetProjectFilePathComponent);
this.formBuilder.removeFormItem(this.targetProjectStructureComponent);
this.formBuilder.addFormItem(this.targetServerComponent, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.addFormItem(this.targetDatabaseComponent, { horizontal: true, titleFontSize: titleFontSize });
await this.populateServerDropdown(true);
});
// show project directory browser
projectRadioButton.onDidClick(async () => {
this.targetEndpointType = mssql.SchemaCompareEndpointType.Project;
this.targetTextBox.value = this.targetProjectFilePath;
this.formBuilder.removeFormItem(this.targetServerComponent);
this.formBuilder.removeFormItem(this.targetDatabaseComponent);
this.formBuilder.removeFormItem(this.targetDacpacComponent);
this.formBuilder.addFormItem(this.targetProjectFilePathComponent, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.addFormItem(this.targetProjectStructureComponent, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// if target is currently a db, show it in the server and db dropdowns
if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) {
databaseRadioButton.checked = true;
this.targetIsDacpac = false;
} else {
this.targetEndpointType = mssql.SchemaCompareEndpointType.Database;
} else if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Dacpac) {
dacpacRadioButton.checked = true;
this.targetIsDacpac = true;
this.targetEndpointType = mssql.SchemaCompareEndpointType.Dacpac;
} else if (this.schemaCompareMainWindow.targetEndpointInfo) {
projectRadioButton.checked = true;
this.targetEndpointType = mssql.SchemaCompareEndpointType.Project;
}
let radioButtons = [databaseRadioButton, dacpacRadioButton];
if (vscode.extensions.getExtension(loc.sqlDatabaseProjectExtensionId)) {
radioButtons.push(projectRadioButton);
}
let flexRadioButtonsModel = this.view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.withItems([dacpacRadioButton, databaseRadioButton]
)
.withItems(radioButtons)
.withProps({ ariaRole: 'radiogroup' })
.component();
@@ -459,18 +641,83 @@ export class SchemaCompareDialog {
}
private async shouldEnableOkayButton(): Promise<boolean> {
let sourcefilled = (this.sourceIsDacpac && await this.existsDacpac(this.sourceTextBox.value))
|| (!this.sourceIsDacpac && !isNullOrUndefined(this.sourceDatabaseDropdown.value) && this.sourceDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.sourceDbEditable)) !== -1);
let targetfilled = (this.targetIsDacpac && await this.existsDacpac(this.targetTextBox.value))
|| (!this.targetIsDacpac && !isNullOrUndefined(this.targetDatabaseDropdown.value) && this.targetDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.targetDbEditable)) !== -1);
let sourcefilled = (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Dacpac && await this.existsDacpac(this.sourceTextBox.value))
|| (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Project && this.existsProjectFile(this.sourceTextBox.value))
|| (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Database && !isNullOrUndefined(this.sourceDatabaseDropdown.value) && this.sourceDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.sourceDbEditable)) !== -1);
let targetfilled = (this.targetEndpointType === mssql.SchemaCompareEndpointType.Dacpac && await this.existsDacpac(this.targetTextBox.value))
|| (this.targetEndpointType === mssql.SchemaCompareEndpointType.Project && this.existsProjectFile(this.targetTextBox.value))
|| (this.targetEndpointType === mssql.SchemaCompareEndpointType.Database && !isNullOrUndefined(this.targetDatabaseDropdown.value) && this.targetDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.targetDbEditable)) !== -1);
return sourcefilled && targetfilled;
}
public async handleOkButtonClick(): Promise<void> {
await this.execute();
this.dispose();
}
protected showErrorMessage(message: string): void {
this.dialog.message = {
text: message,
level: getAzdataApi()!.window.MessageLevel.Error
};
}
async validate(): Promise<boolean> {
try {
// check project extension is installed
if (!vscode.extensions.getExtension(loc.sqlDatabaseProjectExtensionId) &&
(this.sourceEndpointType === mssql.SchemaCompareEndpointType.Project ||
this.targetEndpointType === mssql.SchemaCompareEndpointType.Project)) {
this.showErrorMessage(loc.noProjectExtension);
return false;
}
// check Database Schema Providers are set and valid
if (this.sourceEndpointType === mssql.SchemaCompareEndpointType.Project) {
try {
await this.getDsp(this.sourceTextBox.value);
} catch (err) {
this.showErrorMessage(loc.dspErrorSource);
}
}
if (this.targetEndpointType === mssql.SchemaCompareEndpointType.Project) {
try {
await this.getDsp(this.targetTextBox.value);
} catch (err) {
this.showErrorMessage(loc.dspErrorTarget);
}
}
return true;
} catch (e) {
this.showErrorMessage(e?.message ? e.message : e);
return false;
}
}
private dispose(): void {
this.toDispose.forEach(disposable => disposable.dispose());
}
private async existsDacpac(filename: string): Promise<boolean> {
return !isNullOrUndefined(filename) && await exists(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac'));
}
private async existsProjectFile(filename: string): Promise<boolean> {
return !isNullOrUndefined(filename) && await exists(filename) && (filename.toLocaleLowerCase().endsWith('.sqlproj'));
}
private async getTargetScripts(source: boolean): Promise<string[]> {
const projectFilePath = source ? this.sourceTextBox.value : this.targetTextBox.value;
return await vscode.commands.executeCommand(loc.sqlDatabaseProjectsGetTargetScripts, projectFilePath);
}
private async getDsp(projectFilePath: string): Promise<string> {
return await vscode.commands.executeCommand(loc.sqlDatabaseProjectsGetDsp, projectFilePath);
}
protected createSourceServerDropdown(): azdata.FormComponent {
this.sourceServerDropdown = this.view.modelBuilder.dropDown().withProps(
{
@@ -540,9 +787,7 @@ export class SchemaCompareDialog {
width: this.textBoxWidth
}
).component();
this.targetConnectionButton = this.createConnectionButton(true);
this.targetServerDropdown.onValueChanged(async (value) => {
if (value.selected && this.targetServerDropdown.values.findIndex(x => this.matchesValue(x, value.selected)) === -1) {
await this.targetDatabaseDropdown.updateProperties({
@@ -555,10 +800,8 @@ export class SchemaCompareDialog {
await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection, true);
}
});
// don't await so that dialog loading won't be blocked. Dropdown will show loading indicator until it is populated
this.populateServerDropdown(true);
return {
component: this.targetServerDropdown,
title: loc.ServerDropdownLabel,