mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -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 {
|
||||
|
||||
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,
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
|
||||
import { SchemaCompareMainWindow } from './schemaCompareMainWindow';
|
||||
|
||||
export async function activate(extensionContext: vscode.ExtensionContext): Promise<void> {
|
||||
vscode.commands.registerCommand('schemaCompare.start', async (context: any) => { await new SchemaCompareMainWindow(undefined, extensionContext, undefined).start(context); });
|
||||
vscode.commands.registerCommand('schemaCompare.start', async (sourceContext: any, targetContext: any = undefined, comparisonResult: any = undefined) => { await new SchemaCompareMainWindow(undefined, extensionContext, undefined).start(sourceContext, targetContext, comparisonResult); });
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
|
||||
@@ -14,9 +14,11 @@ export const TargetTitle: string = localize('schemaCompareDialog.TargetTitle', "
|
||||
export const FileTextBoxLabel: string = localize('schemaCompareDialog.fileTextBoxLabel', "File");
|
||||
export const DacpacRadioButtonLabel: string = localize('schemaCompare.dacpacRadioButtonLabel', "Data-tier Application File (.dacpac)");
|
||||
export const DatabaseRadioButtonLabel: string = localize('schemaCompare.databaseButtonLabel', "Database");
|
||||
export const ProjectRadioButtonLabel: string = localize('schemaCompare.projectButtonLabel', "Database Project");
|
||||
export const RadioButtonsLabel: string = localize('schemaCompare.radioButtonsLabel', "Type");
|
||||
export const ServerDropdownLabel: string = localize('schemaCompareDialog.serverDropdownTitle', "Server");
|
||||
export const DatabaseDropdownLabel: string = localize('schemaCompareDialog.databaseDropdownTitle', "Database");
|
||||
export const StructureDropdownLabel: string = localize('schemaCompareDialog.structureDropdownLabel', "Folder Structure");
|
||||
export const SchemaCompareLabel: string = localize('schemaCompare.dialogTitle', "Schema Compare");
|
||||
export const differentSourceMessage: string = localize('schemaCompareDialog.differentSourceMessage', "A different source schema has been selected. Compare to see the comparison?");
|
||||
export const differentTargetMessage: string = localize('schemaCompareDialog.differentTargetMessage', "A different target schema has been selected. Compare to see the comparison?");
|
||||
@@ -31,6 +33,12 @@ export const sourceServer: string = localize('schemaCompareDialog.sourceServerDr
|
||||
export const targetServer: string = localize('schemaCompareDialog.targetServerDropdown', "Target Server");
|
||||
export const defaultText: string = localize('schemaCompareDialog.defaultUser', "default");
|
||||
export const open: string = localize('schemaCompare.openFile', "Open");
|
||||
export const targetStructure = localize('targetStructure', "Target Folder Structure");
|
||||
export const file = localize('file', "File");
|
||||
export const flat = localize('flat', "Flat");
|
||||
export const objectType = localize('objectType', "Object Type");
|
||||
export const schema = localize('schema', "Schema");
|
||||
export const schemaObjectType = localize('schemaObjectType', "Schema/Object Type");
|
||||
export const selectSourceFile: string = localize('schemaCompare.selectSourceFile', "Select source file");
|
||||
export const selectTargetFile: string = localize('schemaCompare.selectTargetFile', "Select target file");
|
||||
export const ResetButtonText: string = localize('SchemaCompareOptionsDialog.Reset', "Reset");
|
||||
@@ -61,7 +69,7 @@ export const include: string = localize('schemaCompare.includeColumnName', "Incl
|
||||
export const action: string = localize('schemaCompare.actionColumn', "Action");
|
||||
export const targetName: string = localize('schemaCompare.targetNameColumn', "Target Name");
|
||||
export const generateScriptDisabled: string = localize('schemaCompare.generateScriptButtonDisabledTitle', "Generate script is enabled when the target is a database");
|
||||
export const applyDisabled: string = localize('schemaCompare.applyButtonDisabledTitle', "Apply is enabled when the target is a database");
|
||||
export const applyDisabled: string = localize('schemaCompare.applyButtonDisabledTitle', "Apply is enabled when the target is a database or database project");
|
||||
export function cannotExcludeMessageDependent(diffEntryName: string, firstDependentName: string): string { return localize('schemaCompare.cannotExcludeMessageWithDependent', "Cannot exclude {0}. Included dependents exist, such as {1}", diffEntryName, firstDependentName); }
|
||||
export function cannotIncludeMessageDependent(diffEntryName: string, firstDependentName: string): string { return localize('schemaCompare.cannotIncludeMessageWithDependent', "Cannot include {0}. Excluded dependents exist, such as {1}", diffEntryName, firstDependentName); }
|
||||
export function cannotExcludeMessage(diffEntryName: string): string { return localize('schemaCompare.cannotExcludeMessage', "Cannot exclude {0}. Included dependents exist", diffEntryName); }
|
||||
@@ -318,3 +326,20 @@ export function cancelErrorMessage(errorMessage: string): string { return locali
|
||||
export function generateScriptErrorMessage(errorMessage: string): string { return localize('schemaCompare.generateScriptErrorMessage', "Generate script failed: '{0}'", (errorMessage) ? errorMessage : 'Unknown'); }
|
||||
export function applyErrorMessage(errorMessage: string): string { return localize('schemaCompare.updateErrorMessage', "Schema Compare Apply failed '{0}'", errorMessage ? errorMessage : 'Unknown'); }
|
||||
export function openScmpErrorMessage(errorMessage: string): string { return localize('schemaCompare.openScmpErrorMessage', "Open scmp failed: '{0}'", (errorMessage) ? errorMessage : 'Unknown'); }
|
||||
export const applyError: string = localize('schemaCompare.applyError', "There was an error updating the project");
|
||||
export const dspErrorSource: string = localize('schemaCompareDialog.dspErrorSource', "The source .sqlproj file does not specify a database schema component");
|
||||
export const dspErrorTarget: string = localize('schemaCompareDialog.dspErrorTarget', "The target .sqlproj file does not specify a database schema component");
|
||||
export const noProjectExtension: string = localize('schemaCompareDialog.noProjectExtension', "The sql-database-projects extension is required to perform schema comparison with database projects");
|
||||
export const noProjectExtensionApply: string = localize('schemaCompareDialog.noProjectExtensionApply', "The sql-database-projects extension is required to apply changes to a project");
|
||||
|
||||
// Information messages
|
||||
export const applySuccess: string = localize('schemaCompare.applySuccess', "Project was successfully updated");
|
||||
|
||||
// Extensions
|
||||
export const sqlDatabaseProjectExtensionId: string = 'microsoft.sql-database-projects';
|
||||
|
||||
// Commands
|
||||
export const sqlDatabaseProjectsGetTargetScripts: string = 'sqlDatabaseProjects.schemaCompareGetTargetScripts';
|
||||
export const sqlDatabaseProjectsGetDsp: string = 'sqlDatabaseProjects.schemaCompareGetDsp';
|
||||
export const sqlDatabaseProjectsPublishChanges: string = 'sqlDatabaseProjects.schemaComparePublishProjectChanges';
|
||||
export const sqlDatabaseProjectsShowProjectsView: string = 'sqlDatabaseProjects.schemaCompareShowProjectsView';
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as mssql from '../../mssql';
|
||||
import * as loc from './localizedConstants';
|
||||
import { SchemaCompareOptionsDialog } from './dialogs/schemaCompareOptionsDialog';
|
||||
import { TelemetryReporter, TelemetryViews } from './telemetry';
|
||||
import { getTelemetryErrorType, getEndpointName, verifyConnectionAndGetOwnerUri, getRootPath } from './utils';
|
||||
import { getTelemetryErrorType, getEndpointName, verifyConnectionAndGetOwnerUri, getRootPath, getSchemaCompareEndpointString } from './utils';
|
||||
import { SchemaCompareDialog } from './dialogs/schemaCompareDialog';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
|
||||
@@ -81,14 +81,33 @@ export class SchemaCompareMainWindow {
|
||||
this.editor = azdata.workspace.createModelViewEditor(loc.SchemaCompareLabel, { retainContextWhenHidden: true, supportsSave: true, resourceName: schemaCompareResourceName }, 'SchemaCompareEditor');
|
||||
}
|
||||
|
||||
// schema compare can get started with three contexts for the source:
|
||||
// schema compare can get started with four contexts for the source:
|
||||
// 1. undefined
|
||||
// 2. connection profile
|
||||
// 3. dacpac
|
||||
public async start(context: any): Promise<void> {
|
||||
// if schema compare was launched from a db, set that as the source
|
||||
let profile = context ? <azdata.IConnectionProfile>context.connectionProfile : undefined;
|
||||
let sourceDacpac = context as string;
|
||||
// 4. project
|
||||
public async start(sourceContext: any, targetContext: mssql.SchemaCompareEndpointInfo = undefined, comparisonResult: mssql.SchemaCompareResult = undefined): Promise<void> {
|
||||
const targetIsSetAsProject: boolean = targetContext && targetContext.endpointType === mssql.SchemaCompareEndpointType.Project;
|
||||
|
||||
// if schema compare was launched from a db or a connection profile, set that as the source
|
||||
let profile: azdata.IConnectionProfile;
|
||||
|
||||
if (targetIsSetAsProject) {
|
||||
profile = sourceContext;
|
||||
this.targetEndpointInfo = targetContext;
|
||||
} else {
|
||||
profile = sourceContext ? <azdata.IConnectionProfile>sourceContext.connectionProfile : undefined;
|
||||
}
|
||||
|
||||
let sourceDacpac = undefined;
|
||||
let sourceProject = undefined;
|
||||
|
||||
if (!profile && sourceContext as string && (sourceContext as string).endsWith('.dacpac')) {
|
||||
sourceDacpac = sourceContext as string;
|
||||
} else if (!profile) {
|
||||
sourceProject = sourceContext as string;
|
||||
}
|
||||
|
||||
if (profile) {
|
||||
let ownerUri = await azdata.connection.getUriForConnection((profile.id));
|
||||
let usr = profile.userName;
|
||||
@@ -106,9 +125,9 @@ export class SchemaCompareMainWindow {
|
||||
connectionDetails: undefined,
|
||||
connectionName: profile.connectionName,
|
||||
projectFilePath: '',
|
||||
folderStructure: '',
|
||||
targetScripts: [],
|
||||
dataSchemaProvider: ''
|
||||
dataSchemaProvider: '',
|
||||
folderStructure: ''
|
||||
};
|
||||
} else if (sourceDacpac) {
|
||||
this.sourceEndpointInfo = {
|
||||
@@ -120,9 +139,23 @@ export class SchemaCompareMainWindow {
|
||||
packageFilePath: sourceDacpac,
|
||||
connectionDetails: undefined,
|
||||
projectFilePath: '',
|
||||
folderStructure: '',
|
||||
targetScripts: [],
|
||||
dataSchemaProvider: ''
|
||||
dataSchemaProvider: '',
|
||||
folderStructure: ''
|
||||
};
|
||||
} else if (sourceProject) {
|
||||
this.sourceEndpointInfo = {
|
||||
endpointType: mssql.SchemaCompareEndpointType.Project,
|
||||
packageFilePath: '',
|
||||
serverDisplayName: '',
|
||||
serverName: '',
|
||||
databaseName: '',
|
||||
ownerUri: '',
|
||||
connectionDetails: undefined,
|
||||
projectFilePath: sourceProject,
|
||||
targetScripts: [],
|
||||
dataSchemaProvider: undefined,
|
||||
folderStructure: ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -131,6 +164,10 @@ export class SchemaCompareMainWindow {
|
||||
this.registerContent(),
|
||||
this.editor.openEditor()
|
||||
]);
|
||||
|
||||
if (targetIsSetAsProject) {
|
||||
await this.execute(comparisonResult);
|
||||
}
|
||||
}
|
||||
|
||||
private async registerContent(): Promise<void> {
|
||||
@@ -170,7 +207,8 @@ export class SchemaCompareMainWindow {
|
||||
this.createSourceAndTargetButtons();
|
||||
|
||||
this.sourceName = getEndpointName(this.sourceEndpointInfo);
|
||||
this.targetName = ' ';
|
||||
this.targetName = getEndpointName(this.targetEndpointInfo);
|
||||
|
||||
this.sourceNameComponent = this.view.modelBuilder.inputBox().withProps({
|
||||
value: this.sourceName,
|
||||
title: this.sourceName,
|
||||
@@ -283,21 +321,29 @@ export class SchemaCompareMainWindow {
|
||||
this.deploymentOptions = deploymentOptions;
|
||||
}
|
||||
|
||||
public async execute() {
|
||||
TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonStarted');
|
||||
public async execute(comparisonResult: mssql.SchemaCompareCompletionResult = undefined) {
|
||||
const service = await this.getService();
|
||||
|
||||
if (comparisonResult) {
|
||||
this.operationId = comparisonResult.operationId;
|
||||
this.comparisonResult = comparisonResult;
|
||||
this.flexModel.removeItem(this.startText);
|
||||
} else {
|
||||
TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonStarted');
|
||||
|
||||
if (!this.operationId) {
|
||||
// create once per page
|
||||
this.operationId = generateGuid();
|
||||
}
|
||||
|
||||
this.comparisonResult = await service.schemaCompare(this.operationId, this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions);
|
||||
|
||||
if (!this.comparisonResult || !this.comparisonResult.success) {
|
||||
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFailed', undefined, getTelemetryErrorType(this.comparisonResult?.errorMessage))
|
||||
.withAdditionalProperties({
|
||||
operationId: this.comparisonResult.operationId
|
||||
}).send();
|
||||
|
||||
vscode.window.showErrorMessage(loc.compareErrorMessage(this.comparisonResult?.errorMessage));
|
||||
|
||||
// reset state so a new comparison can be made
|
||||
@@ -305,11 +351,13 @@ export class SchemaCompareMainWindow {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFinished')
|
||||
.withAdditionalProperties({
|
||||
'endTime': Date.now().toString(),
|
||||
'operationId': this.comparisonResult.operationId
|
||||
}).send();
|
||||
}
|
||||
|
||||
let data = this.getAllDifferences(this.comparisonResult.differences);
|
||||
|
||||
@@ -371,9 +419,15 @@ export class SchemaCompareMainWindow {
|
||||
// only enable generate script button if the target is a db
|
||||
if (this.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) {
|
||||
this.generateScriptButton.enabled = true;
|
||||
this.applyButton.enabled = true;
|
||||
} else {
|
||||
this.generateScriptButton.title = loc.generateScriptDisabled;
|
||||
}
|
||||
|
||||
// only enable apply button if the target is a db or a project
|
||||
if (this.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database ||
|
||||
this.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Project) {
|
||||
this.applyButton.enabled = true;
|
||||
} else {
|
||||
this.applyButton.title = loc.applyDisabled;
|
||||
}
|
||||
} else {
|
||||
@@ -770,7 +824,6 @@ export class SchemaCompareMainWindow {
|
||||
}
|
||||
|
||||
public async publishChanges(): Promise<void> {
|
||||
|
||||
// need only yes button - since the modal dialog has a default cancel
|
||||
const yesString = loc.YesButtonText;
|
||||
await vscode.window.showWarningMessage(loc.applyConfirmation, { modal: true }, yesString).then(async (result) => {
|
||||
@@ -785,13 +838,25 @@ export class SchemaCompareMainWindow {
|
||||
this.setButtonsForRecompare();
|
||||
|
||||
const service: mssql.ISchemaCompareService = await this.getService();
|
||||
const result = await service.schemaComparePublishChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute);
|
||||
let result: azdata.ResultStatus | undefined = undefined;
|
||||
|
||||
switch (this.targetEndpointInfo.endpointType) {
|
||||
case mssql.SchemaCompareEndpointType.Database:
|
||||
result = await service.schemaComparePublishDatabaseChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute);
|
||||
break;
|
||||
case mssql.SchemaCompareEndpointType.Project: // Project apply needs sql-database-projects updates in (circular dependency; coming next) // TODO: re-add this and show project logic below
|
||||
case mssql.SchemaCompareEndpointType.Dacpac: // Dacpac is an invalid publish target
|
||||
default:
|
||||
throw new Error(`Unsupported SchemaCompareEndpointType: ${getSchemaCompareEndpointString(this.targetEndpointInfo.endpointType)}`);
|
||||
}
|
||||
|
||||
if (!result || !result.success) {
|
||||
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyFailed', undefined, getTelemetryErrorType(result.errorMessage))
|
||||
|
||||
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyFailed', undefined, getTelemetryErrorType(result?.errorMessage))
|
||||
.withAdditionalProperties({
|
||||
'operationId': this.comparisonResult.operationId
|
||||
}).send();
|
||||
vscode.window.showErrorMessage(loc.applyErrorMessage(result.errorMessage));
|
||||
vscode.window.showErrorMessage(loc.applyErrorMessage(result?.errorMessage));
|
||||
|
||||
// reenable generate script and apply buttons if apply failed
|
||||
this.generateScriptButton.enabled = true;
|
||||
@@ -799,6 +864,7 @@ export class SchemaCompareMainWindow {
|
||||
this.applyButton.enabled = true;
|
||||
this.applyButton.title = loc.applyEnabledMessage;
|
||||
}
|
||||
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyEnded')
|
||||
.withAdditionalProperties({
|
||||
'endTime': Date.now().toString(),
|
||||
@@ -1090,11 +1156,15 @@ export class SchemaCompareMainWindow {
|
||||
}
|
||||
|
||||
private setButtonStatesForNoChanges(enableButtons: boolean): void {
|
||||
// generate script and apply can only be enabled if the target is a database
|
||||
if (this.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) {
|
||||
// generate script and apply can only be enabled if the target is a database or project
|
||||
if (this.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database ||
|
||||
this.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Project) {
|
||||
this.applyButton.enabled = enableButtons;
|
||||
this.generateScriptButton.enabled = enableButtons;
|
||||
this.applyButton.title = enableButtons ? loc.applyEnabledMessage : loc.applyNoChangesMessage;
|
||||
}
|
||||
|
||||
if (this.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) {
|
||||
this.generateScriptButton.enabled = enableButtons;
|
||||
this.generateScriptButton.title = enableButtons ? loc.generateScriptEnabledMessage : loc.generateScriptNoChangesMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('SchemaCompareMainWindow.results @DacFx@', function (): void {
|
||||
|
||||
it('Should show error if publish changes fails', async function (): Promise<void> {
|
||||
let service = createServiceMock();
|
||||
service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
|
||||
service.setup(x => x.schemaComparePublishDatabaseChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
|
||||
success: false,
|
||||
errorMessage: 'error1'
|
||||
}));
|
||||
@@ -121,7 +121,7 @@ describe('SchemaCompareMainWindow.results @DacFx@', function (): void {
|
||||
await schemaCompareResult.start(undefined);
|
||||
|
||||
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
|
||||
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
|
||||
schemaCompareResult.targetEndpointInfo = setDatabaseEndpointInfo();
|
||||
await schemaCompareResult.execute();
|
||||
await schemaCompareResult.publishChanges();
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('SchemaCompareMainWindow.results @DacFx@', function (): void {
|
||||
|
||||
it('Should show not error if publish changes succeed', async function (): Promise<void> {
|
||||
let service = createServiceMock();
|
||||
service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
|
||||
service.setup(x => x.schemaComparePublishDatabaseChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
|
||||
success: true,
|
||||
errorMessage: ''
|
||||
}));
|
||||
@@ -140,7 +140,7 @@ describe('SchemaCompareMainWindow.results @DacFx@', function (): void {
|
||||
await schemaCompareResult.start(undefined);
|
||||
|
||||
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
|
||||
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
|
||||
schemaCompareResult.targetEndpointInfo = setDatabaseEndpointInfo();
|
||||
await schemaCompareResult.execute();
|
||||
await schemaCompareResult.publishChanges();
|
||||
should(showErrorMessageSpy.notCalled).be.true();
|
||||
@@ -343,7 +343,7 @@ describe('SchemaCompareMainWindow.results @DacFx@', function (): void {
|
||||
|
||||
it('Should not show error if user does not want to publish', async function (): Promise<void> {
|
||||
let service = createServiceMock();
|
||||
service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
|
||||
service.setup(x => x.schemaComparePublishDatabaseChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
|
||||
success: true,
|
||||
errorMessage: ''
|
||||
}));
|
||||
|
||||
@@ -98,7 +98,7 @@ export const mockDacpacEndpoint: mssql.SchemaCompareEndpointInfo = {
|
||||
projectFilePath: '',
|
||||
folderStructure: '',
|
||||
targetScripts: [],
|
||||
dataSchemaProvider: ''
|
||||
dataSchemaProvider: '',
|
||||
};
|
||||
|
||||
export const mockDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {
|
||||
@@ -112,7 +112,7 @@ export const mockDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {
|
||||
projectFilePath: '',
|
||||
folderStructure: '',
|
||||
targetScripts: [],
|
||||
dataSchemaProvider: ''
|
||||
dataSchemaProvider: '',
|
||||
};
|
||||
|
||||
export async function shouldThrowSpecificError(block: Function, expectedMessage: string, details?: string) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import type * as azdataType from 'azdata'; // eslint-disable-line no-duplicate-imports
|
||||
import * as vscode from 'vscode';
|
||||
import * as mssql from '../../mssql';
|
||||
import * as os from 'os';
|
||||
@@ -39,6 +40,19 @@ export function getTelemetryErrorType(msg: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function getSchemaCompareEndpointString(endpointType: mssql.SchemaCompareEndpointType): string {
|
||||
switch (endpointType) {
|
||||
case mssql.SchemaCompareEndpointType.Database:
|
||||
return 'Database';
|
||||
case mssql.SchemaCompareEndpointType.Dacpac:
|
||||
return 'Dacpac';
|
||||
case mssql.SchemaCompareEndpointType.Project:
|
||||
return 'Project';
|
||||
default:
|
||||
return `Unknown: ${endpointType}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate endpoint name depending on if the endpoint is a dacpac or a database
|
||||
* @param endpoint endpoint to get the name of
|
||||
@@ -64,8 +78,11 @@ export function getEndpointName(endpoint: mssql.SchemaCompareEndpointInfo): stri
|
||||
return ' ';
|
||||
}
|
||||
|
||||
} else {
|
||||
} else if (endpoint.endpointType === mssql.SchemaCompareEndpointType.Dacpac) {
|
||||
return endpoint.packageFilePath;
|
||||
|
||||
} else {
|
||||
return endpoint.projectFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,3 +161,24 @@ export async function exists(path: string): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to load the azdata API - but gracefully handle the failure in case we're running
|
||||
// in a context where the API doesn't exist (such as VS Code)
|
||||
let azdataApi: typeof azdataType | undefined = undefined;
|
||||
try {
|
||||
azdataApi = require('azdata');
|
||||
if (!azdataApi?.version) {
|
||||
// webpacking makes the require return an empty object instead of throwing an error so make sure we clear the var
|
||||
azdataApi = undefined;
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the azdata API if it's available in the context this extension is running in.
|
||||
* @returns The azdata API if it's available
|
||||
*/
|
||||
export function getAzdataApi(): typeof azdataType | undefined {
|
||||
return azdataApi;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user