Add ability to change source and target to Schema Compare (#6026)

* add ability to change source and target

* addressing comments

* fixes after rebasing

* add check for user

* bump extension version
This commit is contained in:
Kim Santiago
2019-06-19 15:42:46 -07:00
committed by GitHub
parent 453caa92d4
commit 32313c71e4
9 changed files with 307 additions and 140 deletions

View File

@@ -39,6 +39,7 @@ if (context.RunTest) {
let source: azdata.SchemaCompareEndpointInfo = { let source: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Dacpac, endpointType: azdata.SchemaCompareEndpointType.Dacpac,
packageFilePath: dacpac1, packageFilePath: dacpac1,
serverDisplayName: '',
serverName: '', serverName: '',
databaseName: '', databaseName: '',
ownerUri: '', ownerUri: '',
@@ -46,6 +47,7 @@ if (context.RunTest) {
let target: azdata.SchemaCompareEndpointInfo = { let target: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Dacpac, endpointType: azdata.SchemaCompareEndpointType.Dacpac,
packageFilePath: dacpac2, packageFilePath: dacpac2,
serverDisplayName: '',
serverName: '', serverName: '',
databaseName: '', databaseName: '',
ownerUri: '', ownerUri: '',
@@ -86,6 +88,7 @@ if (context.RunTest) {
let source: azdata.SchemaCompareEndpointInfo = { let source: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Database, endpointType: azdata.SchemaCompareEndpointType.Database,
packageFilePath: '', packageFilePath: '',
serverDisplayName: '',
serverName: server.serverName, serverName: server.serverName,
databaseName: sourceDB, databaseName: sourceDB,
ownerUri: ownerUri, ownerUri: ownerUri,
@@ -93,6 +96,7 @@ if (context.RunTest) {
let target: azdata.SchemaCompareEndpointInfo = { let target: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Database, endpointType: azdata.SchemaCompareEndpointType.Database,
packageFilePath: '', packageFilePath: '',
serverDisplayName: '',
serverName: server.serverName, serverName: server.serverName,
databaseName: targetDB, databaseName: targetDB,
ownerUri: ownerUri, ownerUri: ownerUri,
@@ -137,6 +141,7 @@ if (context.RunTest) {
let source: azdata.SchemaCompareEndpointInfo = { let source: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Dacpac, endpointType: azdata.SchemaCompareEndpointType.Dacpac,
packageFilePath: dacpac1, packageFilePath: dacpac1,
serverDisplayName: '',
serverName: '', serverName: '',
databaseName: '', databaseName: '',
ownerUri: ownerUri, ownerUri: ownerUri,
@@ -144,6 +149,7 @@ if (context.RunTest) {
let target: azdata.SchemaCompareEndpointInfo = { let target: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Database, endpointType: azdata.SchemaCompareEndpointType.Database,
packageFilePath: '', packageFilePath: '',
serverDisplayName: '',
serverName: server.serverName, serverName: server.serverName,
databaseName: targetDB, databaseName: targetDB,
ownerUri: ownerUri, ownerUri: ownerUri,

View File

@@ -2,7 +2,7 @@
"name": "schema-compare", "name": "schema-compare",
"displayName": "%displayName%", "displayName": "%displayName%",
"description": "%description%", "description": "%description%",
"version": "0.3.0", "version": "0.4.0",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"engines": { "engines": {

View File

@@ -7,7 +7,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { SchemaCompareDialog } from '../dialogs/schemaCompareDialog'; import { SchemaCompareResult } from '../schemaCompareResult';
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
@@ -32,7 +32,7 @@ export default class MainController implements vscode.Disposable {
} }
private initializeSchemaCompareDialog(): void { private initializeSchemaCompareDialog(): void {
azdata.tasks.registerTask('schemaCompare.start', (profile: azdata.IConnectionProfile) => new SchemaCompareDialog().openDialog(profile)); azdata.tasks.registerTask('schemaCompare.start', (profile: azdata.IConnectionProfile) => new SchemaCompareResult().start(profile));
} }
public dispose(): void { public dispose(): void {

View File

@@ -12,6 +12,7 @@ import { SchemaCompareResult } from '../schemaCompareResult';
import { isNullOrUndefined } from 'util'; import { isNullOrUndefined } from 'util';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { Telemetry } from '../telemetry'; import { Telemetry } from '../telemetry';
import { getEndpointName } from '../utils';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const OkButtonText: string = localize('schemaCompareDialog.ok', 'Ok'); const OkButtonText: string = localize('schemaCompareDialog.ok', 'Ok');
@@ -26,6 +27,11 @@ const ServerDropdownLabel: string = localize('schemaCompareDialog.serverDropdown
const DatabaseDropdownLabel: string = localize('schemaCompareDialog.databaseDropdownTitle', 'Database'); const DatabaseDropdownLabel: string = localize('schemaCompareDialog.databaseDropdownTitle', 'Database');
const NoActiveConnectionsLabel: string = localize('schemaCompare.noActiveConnectionsText', 'No active connections'); const NoActiveConnectionsLabel: string = localize('schemaCompare.noActiveConnectionsText', 'No active connections');
const SchemaCompareLabel: string = localize('schemaCompare.dialogTitle', 'Schema Compare'); const SchemaCompareLabel: string = localize('schemaCompare.dialogTitle', 'Schema Compare');
const differentSourceMessage: string = localize('schemaCompareDialog.differentSourceMessage', 'A different source schema has been selected. Compare to see the comparison?');
const differentTargetMessage: string = localize('schemaCompareDialog.differentTargetMessage', 'A different target schema has been selected. Compare to see the comparison?');
const differentSourceTargetMessage: string = localize('schemaCompareDialog.differentSourceTargetMessage', 'Different source and target schemas have been selected. Compare to see the comparison?');
const YesButtonText: string = localize('schemaCompareDialog.Yes', 'Yes');
const NoButtonText: string = localize('schemaCompareDialog.No', 'No');
const titleFontSize: number = 13; const titleFontSize: number = 13;
export class SchemaCompareDialog { export class SchemaCompareDialog {
@@ -51,10 +57,16 @@ export class SchemaCompareDialog {
private formBuilder: azdata.FormBuilder; private formBuilder: azdata.FormBuilder;
private sourceIsDacpac: boolean; private sourceIsDacpac: boolean;
private targetIsDacpac: boolean; private targetIsDacpac: boolean;
private database: string;
private connectionId: string; private connectionId: string;
private sourceDbEditable: string; private sourceDbEditable: string;
private taregtDbEditable: string; private taregtDbEditable: string;
private previousSource: azdata.SchemaCompareEndpointInfo;
private previousTarget: azdata.SchemaCompareEndpointInfo;
constructor(private schemaCompareResult: SchemaCompareResult) {
this.previousSource = schemaCompareResult.sourceEndpointInfo;
this.previousTarget = schemaCompareResult.targetEndpointInfo;
}
protected initializeDialog(): void { protected initializeDialog(): void {
this.schemaCompareTab = azdata.window.createTab(SchemaCompareLabel); this.schemaCompareTab = azdata.window.createTab(SchemaCompareLabel);
@@ -62,22 +74,14 @@ export class SchemaCompareDialog {
this.dialog.content = [this.schemaCompareTab]; this.dialog.content = [this.schemaCompareTab];
} }
public async openDialog(context: any, dialogName?: string): Promise<void> { public async openDialog(): Promise<void> {
let profile = context ? <azdata.IConnectionProfile>context.connectionProfile : undefined; // connection to use if schema compare wasn't launched from a database or no previous source/target
if (profile) { let connection = await azdata.connection.getCurrentConnection();
this.database = profile.databaseName; if (connection) {
this.connectionId = profile.id; this.connectionId = connection.connectionId;
} else {
let connection = await azdata.connection.getCurrentConnection();
if (connection) {
this.connectionId = connection.connectionId;
this.database = undefined;
}
} }
let event = dialogName ? dialogName : null; this.dialog = azdata.window.createModelViewDialog(SchemaCompareLabel);
this.dialog = azdata.window.createModelViewDialog(SchemaCompareLabel, event);
this.initializeDialog(); this.initializeDialog();
this.dialog.okButton.label = OkButtonText; this.dialog.okButton.label = OkButtonText;
@@ -91,25 +95,21 @@ export class SchemaCompareDialog {
} }
protected async execute(): Promise<void> { protected async execute(): Promise<void> {
let sourceName: string;
let targetName: string;
let sourceEndpointInfo: azdata.SchemaCompareEndpointInfo;
if (this.sourceIsDacpac) { if (this.sourceIsDacpac) {
sourceName = this.sourceTextBox.value; this.schemaCompareResult.sourceEndpointInfo = {
sourceEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Dacpac, endpointType: azdata.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '', serverName: '',
databaseName: '', databaseName: '',
ownerUri: '', ownerUri: '',
packageFilePath: this.sourceTextBox.value packageFilePath: this.sourceTextBox.value
}; };
} else { } else {
sourceName = (this.sourceServerDropdown.value as ConnectionDropdownValue).name + '.' + (<azdata.CategoryValue>this.sourceDatabaseDropdown.value).name;
let ownerUri = await azdata.connection.getUriForConnection((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId); let ownerUri = await azdata.connection.getUriForConnection((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId);
sourceEndpointInfo = { this.schemaCompareResult.sourceEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Database, endpointType: azdata.SchemaCompareEndpointType.Database,
serverDisplayName: (this.sourceServerDropdown.value as ConnectionDropdownValue).displayName,
serverName: (this.sourceServerDropdown.value as ConnectionDropdownValue).name, serverName: (this.sourceServerDropdown.value as ConnectionDropdownValue).name,
databaseName: (<azdata.CategoryValue>this.sourceDatabaseDropdown.value).name, databaseName: (<azdata.CategoryValue>this.sourceDatabaseDropdown.value).name,
ownerUri: ownerUri, ownerUri: ownerUri,
@@ -117,22 +117,21 @@ export class SchemaCompareDialog {
}; };
} }
let targetEndpointInfo: azdata.SchemaCompareEndpointInfo;
if (this.targetIsDacpac) { if (this.targetIsDacpac) {
targetName = this.targetTextBox.value; this.schemaCompareResult.targetEndpointInfo = {
targetEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Dacpac, endpointType: azdata.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '', serverName: '',
databaseName: '', databaseName: '',
ownerUri: '', ownerUri: '',
packageFilePath: this.targetTextBox.value packageFilePath: this.targetTextBox.value
}; };
} else { } else {
targetName = (this.targetServerDropdown.value as ConnectionDropdownValue).name + '.' + (<azdata.CategoryValue>this.targetDatabaseDropdown.value).name;
let ownerUri = await azdata.connection.getUriForConnection((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId); let ownerUri = await azdata.connection.getUriForConnection((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId);
targetEndpointInfo = { this.schemaCompareResult.targetEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Database, endpointType: azdata.SchemaCompareEndpointType.Database,
serverDisplayName: (this.targetServerDropdown.value as ConnectionDropdownValue).displayName,
serverName: (this.targetServerDropdown.value as ConnectionDropdownValue).name, serverName: (this.targetServerDropdown.value as ConnectionDropdownValue).name,
databaseName: (<azdata.CategoryValue>this.targetDatabaseDropdown.value).name, databaseName: (<azdata.CategoryValue>this.targetDatabaseDropdown.value).name,
ownerUri: ownerUri, ownerUri: ownerUri,
@@ -144,8 +143,39 @@ export class SchemaCompareDialog {
'sourceIsDacpac': this.sourceIsDacpac.toString(), 'sourceIsDacpac': this.sourceIsDacpac.toString(),
'targetIsDacpac': this.targetIsDacpac.toString() 'targetIsDacpac': this.targetIsDacpac.toString()
}); });
let schemaCompareResult = new SchemaCompareResult(sourceName, targetName, sourceEndpointInfo, targetEndpointInfo);
schemaCompareResult.start(); // update source and target values that are displayed
this.schemaCompareResult.updateSourceAndTarget();
const sourceEndpointChanged = this.endpointChanged(this.previousSource, this.schemaCompareResult.sourceEndpointInfo);
const targetEndpointChanged = this.endpointChanged(this.previousTarget, this.schemaCompareResult.targetEndpointInfo);
// show recompare message if it isn't the initial population of source and target
if (this.previousSource && this.previousTarget
&& (sourceEndpointChanged || targetEndpointChanged)) {
this.schemaCompareResult.setButtonsForRecompare();
let message = differentSourceMessage;
if (sourceEndpointChanged && targetEndpointChanged) {
message = differentSourceTargetMessage;
} else if (targetEndpointChanged) {
message = differentTargetMessage;
}
vscode.window.showWarningMessage(message, YesButtonText, NoButtonText).then((result) => {
if (result === YesButtonText) {
this.schemaCompareResult.startCompare();
}
});
}
}
private endpointChanged(previousEndpoint: azdata.SchemaCompareEndpointInfo, updatedEndpoint: azdata.SchemaCompareEndpointInfo): boolean {
if (previousEndpoint && updatedEndpoint) {
return getEndpointName(previousEndpoint).toLowerCase() !== getEndpointName(updatedEndpoint).toLowerCase()
|| previousEndpoint.serverDisplayName.toLocaleLowerCase() !== updatedEndpoint.serverDisplayName.toLowerCase();
}
return false;
} }
protected async cancel(): Promise<void> { protected async cancel(): Promise<void> {
@@ -154,6 +184,7 @@ export class SchemaCompareDialog {
private initializeSchemaCompareTab(): void { private initializeSchemaCompareTab(): void {
this.schemaCompareTab.registerContent(async view => { this.schemaCompareTab.registerContent(async view => {
this.sourceTextBox = view.modelBuilder.inputBox().withProperties({ this.sourceTextBox = view.modelBuilder.inputBox().withProperties({
value: this.schemaCompareResult.sourceEndpointInfo ? this.schemaCompareResult.sourceEndpointInfo.packageFilePath : '',
width: 275 width: 275
}).component(); }).component();
@@ -162,6 +193,7 @@ export class SchemaCompareDialog {
}); });
this.targetTextBox = view.modelBuilder.inputBox().withProperties({ this.targetTextBox = view.modelBuilder.inputBox().withProperties({
value: this.schemaCompareResult.targetEndpointInfo ? this.schemaCompareResult.targetEndpointInfo.packageFilePath : '',
width: 275 width: 275
}).component(); }).component();
@@ -185,8 +217,8 @@ export class SchemaCompareDialog {
await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId, true); await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId, true);
} }
this.sourceDacpacComponent = await this.createFileBrowser(view, false); this.sourceDacpacComponent = await this.createFileBrowser(view, false, this.schemaCompareResult.sourceEndpointInfo);
this.targetDacpacComponent = await this.createFileBrowser(view, true); this.targetDacpacComponent = await this.createFileBrowser(view, true, this.schemaCompareResult.targetEndpointInfo);
let sourceRadioButtons = await this.createSourceRadiobuttons(view); let sourceRadioButtons = await this.createSourceRadiobuttons(view);
let targetRadioButtons = await this.createTargetRadiobuttons(view); let targetRadioButtons = await this.createTargetRadiobuttons(view);
@@ -194,63 +226,60 @@ export class SchemaCompareDialog {
this.sourceNoActiveConnectionsText = await this.createNoActiveConnectionsText(view); this.sourceNoActiveConnectionsText = await this.createNoActiveConnectionsText(view);
this.targetNoActiveConnectionsText = await this.createNoActiveConnectionsText(view); this.targetNoActiveConnectionsText = await this.createNoActiveConnectionsText(view);
// if schema compare was launched from a db context menu, set that db as the source let sourceComponents = [];
if (this.database) { let targetComponents = [];
this.formBuilder = <azdata.FormBuilder>view.modelBuilder.formContainer()
.withFormItems([ // start source and target with either dacpac or database selection based on what the previous value was
{ if (this.schemaCompareResult.sourceEndpointInfo && this.schemaCompareResult.sourceEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) {
title: SourceTitle, sourceComponents = [
components: [ sourceRadioButtons,
sourceRadioButtons, this.sourceServerComponent,
this.sourceServerComponent, this.sourceDatabaseComponent
this.sourceDatabaseComponent ];
]
}, {
title: TargetTitle,
components: [
targetRadioButtons,
this.targetDacpacComponent
]
}
], {
horizontal: true,
titleFontSize: titleFontSize
})
.withLayout({
width: '100%',
padding: '10px 10px 0 30px'
});
} else { } else {
this.formBuilder = <azdata.FormBuilder>view.modelBuilder.formContainer() sourceComponents = [
.withFormItems([ sourceRadioButtons,
{ this.sourceDacpacComponent,
title: SourceTitle, ];
components: [
sourceRadioButtons,
this.sourceDacpacComponent,
]
}, {
title: TargetTitle,
components: [
targetRadioButtons,
this.targetDacpacComponent
]
}
], {
horizontal: true,
titleFontSize: titleFontSize
})
.withLayout({
width: '100%',
padding: '10px 10px 0 30px'
});
} }
if (this.schemaCompareResult.targetEndpointInfo && this.schemaCompareResult.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) {
targetComponents = [
targetRadioButtons,
this.targetServerComponent,
this.targetDatabaseComponent
];
} else {
targetComponents = [
targetRadioButtons,
this.targetDacpacComponent,
];
}
this.formBuilder = <azdata.FormBuilder>view.modelBuilder.formContainer()
.withFormItems([
{
title: SourceTitle,
components: sourceComponents
}, {
title: TargetTitle,
components: targetComponents
}
], {
horizontal: true,
titleFontSize: titleFontSize
})
.withLayout({
width: '100%',
padding: '10px 10px 0 30px'
});
let formModel = this.formBuilder.component(); let formModel = this.formBuilder.component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
}); });
} }
private async createFileBrowser(view: azdata.ModelView, isTarget: boolean): Promise<azdata.FormComponent> { private async createFileBrowser(view: azdata.ModelView, isTarget: boolean, endpoint: azdata.SchemaCompareEndpointInfo): Promise<azdata.FormComponent> {
let currentTextbox = isTarget ? this.targetTextBox : this.sourceTextBox; let currentTextbox = isTarget ? this.targetTextBox : this.sourceTextBox;
if (isTarget) { if (isTarget) {
this.targetFileButton = view.modelBuilder.button().withProperties({ this.targetFileButton = view.modelBuilder.button().withProperties({
@@ -265,13 +294,16 @@ export class SchemaCompareDialog {
let currentButton = isTarget ? this.targetFileButton : this.sourceFileButton; let currentButton = isTarget ? this.targetFileButton : this.sourceFileButton;
currentButton.onDidClick(async (click) => { currentButton.onDidClick(async (click) => {
// file browser should open where the current dacpac is or the appropriate default folder
let rootPath = vscode.workspace.rootPath ? vscode.workspace.rootPath : os.homedir(); let rootPath = vscode.workspace.rootPath ? vscode.workspace.rootPath : os.homedir();
let defaultUri = endpoint && endpoint.packageFilePath && existsSync(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath;
let fileUris = await vscode.window.showOpenDialog( let fileUris = await vscode.window.showOpenDialog(
{ {
canSelectFiles: true, canSelectFiles: true,
canSelectFolders: false, canSelectFolders: false,
canSelectMany: false, canSelectMany: false,
defaultUri: vscode.Uri.file(rootPath), defaultUri: vscode.Uri.file(defaultUri),
openLabel: localize('schemaCompare.openFile', 'Open'), openLabel: localize('schemaCompare.openFile', 'Open'),
filters: { filters: {
'dacpac Files': ['dacpac'], 'dacpac Files': ['dacpac'],
@@ -330,7 +362,8 @@ export class SchemaCompareDialog {
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = this.shouldEnableOkayButton();
}); });
if (this.database) { // if source is currently a db, show it in the server and db dropdowns
if (this.schemaCompareResult.sourceEndpointInfo && this.schemaCompareResult.sourceEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) {
databaseRadioButton.checked = true; databaseRadioButton.checked = true;
this.sourceIsDacpac = false; this.sourceIsDacpac = false;
} else { } else {
@@ -384,8 +417,15 @@ export class SchemaCompareDialog {
this.dialog.okButton.enabled = this.shouldEnableOkayButton(); this.dialog.okButton.enabled = this.shouldEnableOkayButton();
}); });
dacpacRadioButton.checked = true; // if target is currently a db, show it in the server and db dropdowns
this.targetIsDacpac = true; if (this.schemaCompareResult.targetEndpointInfo && this.schemaCompareResult.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) {
databaseRadioButton.checked = true;
this.targetIsDacpac = false;
} else {
dacpacRadioButton.checked = true;
this.targetIsDacpac = true;
}
let flexRadioButtonsModel = view.modelBuilder.flexContainer() let flexRadioButtonsModel = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' }) .withLayout({ flexFlow: 'column' })
.withItems([dacpacRadioButton, databaseRadioButton] .withItems([dacpacRadioButton, databaseRadioButton]
@@ -463,7 +503,7 @@ export class SchemaCompareDialog {
protected async populateServerDropdown(isTarget: boolean): Promise<void> { protected async populateServerDropdown(isTarget: boolean): Promise<void> {
let currentDropdown = isTarget ? this.targetServerDropdown : this.sourceServerDropdown; let currentDropdown = isTarget ? this.targetServerDropdown : this.sourceServerDropdown;
let values = await this.getServerValues(); let values = await this.getServerValues(isTarget);
if (values && values.length > 0) { if (values && values.length > 0) {
currentDropdown.updateProperties({ currentDropdown.updateProperties({
@@ -473,13 +513,14 @@ export class SchemaCompareDialog {
} }
} }
protected async getServerValues(): Promise<{ connection: azdata.connection.Connection, displayName: string, name: string }[]> { protected async getServerValues(isTarget: boolean): Promise<{ connection: azdata.connection.Connection, displayName: string, name: string }[]> {
let cons = await azdata.connection.getActiveConnections(); let cons = await azdata.connection.getActiveConnections();
// This user has no active connections // This user has no active connections
if (!cons || cons.length === 0) { if (!cons || cons.length === 0) {
return undefined; return undefined;
} }
let endpointInfo = isTarget ? this.schemaCompareResult.targetEndpointInfo : this.schemaCompareResult.sourceEndpointInfo;
// reverse list so that most recent connections are first // reverse list so that most recent connections are first
cons.reverse(); cons.reverse();
@@ -488,10 +529,6 @@ export class SchemaCompareDialog {
let values = cons.map(c => { let values = cons.map(c => {
count++; count++;
if (c.connectionId === this.connectionId) {
idx = count;
}
let usr = c.options.user; let usr = c.options.user;
let srv = c.options.server; let srv = c.options.server;
@@ -500,10 +537,24 @@ export class SchemaCompareDialog {
} }
let finalName = `${srv} (${usr})`; let finalName = `${srv} (${usr})`;
if (endpointInfo) {
console.error('finalname: ' + finalName + ' endpointname: ' + endpointInfo.serverDisplayName);
}
// use previously selected server or current connection if there is one
if (endpointInfo && endpointInfo.serverName !== null
&& c.options.server.toLowerCase() === endpointInfo.serverName.toLowerCase()
&& finalName.toLowerCase() === endpointInfo.serverDisplayName.toLowerCase()) {
idx = count;
}
else if (c.connectionId === this.connectionId) {
idx = count;
}
return { return {
connection: c, connection: c,
displayName: finalName, displayName: finalName,
name: srv name: srv,
user: usr
}; };
}); });
@@ -569,7 +620,7 @@ export class SchemaCompareDialog {
let currentDropdown = isTarget ? this.targetDatabaseDropdown : this.sourceDatabaseDropdown; let currentDropdown = isTarget ? this.targetDatabaseDropdown : this.sourceDatabaseDropdown;
currentDropdown.updateProperties({ values: [], value: null }); currentDropdown.updateProperties({ values: [], value: null });
let values = await this.getDatabaseValues(connectionId); let values = await this.getDatabaseValues(connectionId, isTarget);
if (values && values.length > 0) { if (values && values.length > 0) {
currentDropdown.updateProperties({ currentDropdown.updateProperties({
values: values, values: values,
@@ -578,13 +629,17 @@ export class SchemaCompareDialog {
} }
} }
protected async getDatabaseValues(connectionId: string): Promise<{ displayName, name }[]> { protected async getDatabaseValues(connectionId: string, isTarget: boolean): Promise<{ displayName, name }[]> {
let endpointInfo = isTarget ? this.schemaCompareResult.targetEndpointInfo : this.schemaCompareResult.sourceEndpointInfo;
let idx = -1; let idx = -1;
let count = -1; let count = -1;
let values = (await azdata.connection.listDatabases(connectionId)).map(db => { let values = (await azdata.connection.listDatabases(connectionId)).map(db => {
count++; count++;
// if schema compare was launched from a db context menu, set that db at the top of the dropdown
if (this.database && db === this.database) { // put currently selected db at the top of the dropdown if there is one
if (endpointInfo && endpointInfo.databaseName !== null
&& db === endpointInfo.databaseName) {
idx = count; idx = count;
} }

View File

@@ -10,7 +10,8 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { SchemaCompareOptionsDialog } from './dialogs/schemaCompareOptionsDialog'; import { SchemaCompareOptionsDialog } from './dialogs/schemaCompareOptionsDialog';
import { Telemetry } from './telemetry'; import { Telemetry } from './telemetry';
import { getTelemetryErrorType } from './utils'; import { getTelemetryErrorType, getEndpointName } from './utils';
import { SchemaCompareDialog } from './dialogs/schemaCompareDialog';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const diffEditorTitle = localize('schemaCompare.CompareDetailsTitle', 'Compare Details'); const diffEditorTitle = localize('schemaCompare.CompareDetailsTitle', 'Compare Details');
const applyConfirmation = localize('schemaCompare.ApplyConfirmation', 'Are you sure you want to update the target?'); const applyConfirmation = localize('schemaCompare.ApplyConfirmation', 'Are you sure you want to update the target?');
@@ -40,6 +41,8 @@ export class SchemaCompareResult {
private optionsButton: azdata.ButtonComponent; private optionsButton: azdata.ButtonComponent;
private generateScriptButton: azdata.ButtonComponent; private generateScriptButton: azdata.ButtonComponent;
private applyButton: azdata.ButtonComponent; private applyButton: azdata.ButtonComponent;
private selectSourceButton: azdata.ButtonComponent;
private selectTargetButton: azdata.ButtonComponent;
private SchemaCompareActionMap: Map<Number, string>; private SchemaCompareActionMap: Map<Number, string>;
private comparisonResult: azdata.SchemaCompareResult; private comparisonResult: azdata.SchemaCompareResult;
private sourceNameComponent: azdata.TableComponent; private sourceNameComponent: azdata.TableComponent;
@@ -50,18 +53,37 @@ export class SchemaCompareResult {
private originalSourceExcludes = new Map<string, azdata.DiffEntry>(); private originalSourceExcludes = new Map<string, azdata.DiffEntry>();
private originalTargetExcludes = new Map<string, azdata.DiffEntry>(); private originalTargetExcludes = new Map<string, azdata.DiffEntry>();
private sourceTargetSwitched = false; private sourceTargetSwitched = false;
private sourceName: string;
private targetName: string;
constructor(private sourceName: string, private targetName: string, private sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, private targetEndpointInfo: azdata.SchemaCompareEndpointInfo) { public sourceEndpointInfo: azdata.SchemaCompareEndpointInfo;
public targetEndpointInfo: azdata.SchemaCompareEndpointInfo;
constructor() {
this.SchemaCompareActionMap = new Map<Number, string>(); this.SchemaCompareActionMap = new Map<Number, string>();
this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Delete] = localize('schemaCompare.deleteAction', 'Delete'); this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Delete] = localize('schemaCompare.deleteAction', 'Delete');
this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Change] = localize('schemaCompare.changeAction', 'Change'); this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Change] = localize('schemaCompare.changeAction', 'Change');
this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Add] = localize('schemaCompare.addAction', 'Add'); this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Add] = localize('schemaCompare.addAction', 'Add');
this.editor = azdata.workspace.createModelViewEditor(localize('schemaCompare.Title', 'Schema Compare'), { retainContextWhenHidden: true, supportsSave: true, resourceName: schemaCompareResourceName }); this.editor = azdata.workspace.createModelViewEditor(localize('schemaCompare.Title', 'Schema Compare'), { retainContextWhenHidden: true, supportsSave: true, resourceName: schemaCompareResourceName });
this.GetDefaultDeploymentOptions(); }
public async start(context: any) {
// if schema compare was launched from a db, set that as the source
let profile = context ? <azdata.IConnectionProfile>context.connectionProfile : undefined;
if (profile) {
let ownerUri = await azdata.connection.getUriForConnection((profile.id));
this.sourceEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Database,
serverDisplayName: `${profile.serverName} ${profile.userName}`,
serverName: profile.serverName,
databaseName: profile.databaseName,
ownerUri: ownerUri,
packageFilePath: ''
};
}
this.editor.registerContent(async view => { this.editor.registerContent(async view => {
this.differencesTable = view.modelBuilder.table().withProperties({ this.differencesTable = view.modelBuilder.table().withProperties({
data: [], data: [],
height: 300 height: 300
@@ -93,7 +115,8 @@ export class SchemaCompareResult {
this.createGenerateScriptButton(view); this.createGenerateScriptButton(view);
this.createApplyButton(view); this.createApplyButton(view);
this.createOptionsButton(view); this.createOptionsButton(view);
this.resetButtons(true); this.createSourceAndTargetButtons(view);
this.resetButtons(false); // disable buttons because source and target aren't both selected yet
let toolBar = view.modelBuilder.toolbarContainer(); let toolBar = view.modelBuilder.toolbarContainer();
toolBar.addToolbarItems([{ toolBar.addToolbarItems([{
@@ -123,12 +146,13 @@ export class SchemaCompareResult {
value: localize('schemaCompare.switchLabel', '➔') value: localize('schemaCompare.switchLabel', '➔')
}).component(); }).component();
this.sourceName = this.sourceEndpointInfo ? `${this.sourceEndpointInfo.serverName}.${this.sourceEndpointInfo.databaseName}` : ' ';
this.sourceNameComponent = view.modelBuilder.table().withProperties({ this.sourceNameComponent = view.modelBuilder.table().withProperties({
columns: [ columns: [
{ {
value: sourceName, value: this.sourceName,
headerCssClass: 'no-borders', headerCssClass: 'no-borders',
toolTip: sourceName toolTip: this.sourceName
}, },
] ]
}).component(); }).component();
@@ -136,18 +160,20 @@ export class SchemaCompareResult {
this.targetNameComponent = view.modelBuilder.table().withProperties({ this.targetNameComponent = view.modelBuilder.table().withProperties({
columns: [ columns: [
{ {
value: targetName, value: ' ',
headerCssClass: 'no-borders', headerCssClass: 'no-borders',
toolTip: targetName toolTip: ''
}, },
] ]
}).component(); }).component();
sourceTargetLabels.addItem(sourceLabel, { CSSStyles: { 'width': '55%', 'margin-left': '15px', 'font-size': 'larger', 'font-weight': 'bold' } }); sourceTargetLabels.addItem(sourceLabel, { CSSStyles: { 'width': '55%', 'margin-left': '15px', 'font-size': 'larger', 'font-weight': 'bold' } });
sourceTargetLabels.addItem(targetLabel, { CSSStyles: { 'width': '45%', 'font-size': 'larger', 'font-weight': 'bold' } }); sourceTargetLabels.addItem(targetLabel, { CSSStyles: { 'width': '45%', 'font-size': 'larger', 'font-weight': 'bold' } });
this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '45%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } }); this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectSourceButton, { CSSStyles: { 'margin-top': '10px' } });
this.sourceTargetFlexLayout.addItem(arrowLabel, { CSSStyles: { 'width': '10%', 'font-size': 'larger', 'text-align-last': 'center' } }); this.sourceTargetFlexLayout.addItem(arrowLabel, { CSSStyles: { 'width': '10%', 'font-size': 'larger', 'text-align-last': 'center' } });
this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '45%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } }); this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectTargetButton, { CSSStyles: { 'margin-top': '10px' } });
this.loader = view.modelBuilder.loadingComponent().component(); this.loader = view.modelBuilder.loadingComponent().component();
this.waitText = view.modelBuilder.text().withProperties({ this.waitText = view.modelBuilder.text().withProperties({
@@ -155,7 +181,7 @@ export class SchemaCompareResult {
}).component(); }).component();
this.startText = view.modelBuilder.text().withProperties({ this.startText = view.modelBuilder.text().withProperties({
value: localize('schemaCompare.startText', 'Press Compare to start Schema Comparison.') value: localize('schemaCompare.startText', 'To compare two schemas, first select a source schema and target schema, then press Compare.')
}).component(); }).component();
this.noDifferencesLabel = view.modelBuilder.text().withProperties({ this.noDifferencesLabel = view.modelBuilder.text().withProperties({
@@ -175,10 +201,33 @@ export class SchemaCompareResult {
await view.initializeModel(this.flexModel); await view.initializeModel(this.flexModel);
}); });
await this.GetDefaultDeploymentOptions();
this.editor.openEditor();
} }
public start(): void { // update source and target name to display
this.editor.openEditor(); public updateSourceAndTarget() {
this.sourceName = getEndpointName(this.sourceEndpointInfo);
this.targetName = getEndpointName(this.targetEndpointInfo);
this.sourceNameComponent.updateProperty('columns', [
{
value: this.sourceName,
headerCssClass: 'no-borders',
toolTip: this.sourceName
},
]);
this.targetNameComponent.updateProperty('columns', [
{
value: this.targetName,
headerCssClass: 'no-borders',
toolTip: this.targetName
},
]);
// reset buttons to before comparison state
this.resetButtons(true);
} }
// only for test // only for test
@@ -525,10 +574,7 @@ export class SchemaCompareResult {
}); });
// disable apply and generate script buttons because the results are no longer valid after applying the changes // disable apply and generate script buttons because the results are no longer valid after applying the changes
this.generateScriptButton.enabled = false; this.setButtonsForRecompare();
this.generateScriptButton.title = reCompareToRefeshMessage;
this.applyButton.enabled = false;
this.applyButton.title = reCompareToRefeshMessage;
const service = await SchemaCompareResult.getService('MSSQL'); const service = await SchemaCompareResult.getService('MSSQL');
const result = await service.schemaComparePublishChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute); const result = await service.schemaComparePublishChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute);
@@ -572,6 +618,13 @@ export class SchemaCompareResult {
this.applyButton.title = applyEnabledMessage; this.applyButton.title = applyEnabledMessage;
} }
public setButtonsForRecompare(): void {
this.generateScriptButton.enabled = false;
this.applyButton.enabled = false;
this.generateScriptButton.title = reCompareToRefeshMessage;
this.applyButton.title = reCompareToRefeshMessage;
}
private createSwitchButton(view: azdata.ModelView): void { private createSwitchButton(view: azdata.ModelView): void {
this.switchButton = view.modelBuilder.button().withProperties({ this.switchButton = view.modelBuilder.button().withProperties({
label: localize('schemaCompare.switchDirectionButton', 'Switch direction'), label: localize('schemaCompare.switchDirectionButton', 'Switch direction'),
@@ -615,6 +668,30 @@ export class SchemaCompareResult {
}); });
} }
private createSourceAndTargetButtons(view: azdata.ModelView): void {
this.selectSourceButton = view.modelBuilder.button().withProperties({
label: '•••',
title: localize('schemaCompare.sourceButtonTitle', 'Select Source')
}).component();
this.selectSourceButton.onDidClick(() => {
Telemetry.sendTelemetryEvent('SchemaCompareSelectSource');
let dialog = new SchemaCompareDialog(this);
dialog.openDialog();
});
this.selectTargetButton = view.modelBuilder.button().withProperties({
label: '•••',
title: localize('schemaCompare.targetButtonTitle', 'Select Target')
}).component();
this.selectTargetButton.onDidClick(() => {
Telemetry.sendTelemetryEvent('SchemaCompareSelectTarget');
let dialog = new SchemaCompareDialog(this);
dialog.openDialog();
});
}
private setButtonStatesForNoChanges(enableButtons: boolean): void { private setButtonStatesForNoChanges(enableButtons: boolean): void {
// generate script and apply can only be enabled if the target is a database // generate script and apply can only be enabled if the target is a database
if (this.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) { if (this.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) {

View File

@@ -14,19 +14,19 @@ import { SchemaCompareTestService } from './testSchemaCompareService';
// Mock test data // Mock test data
const mockConnectionProfile: azdata.IConnectionProfile = { const mockConnectionProfile: azdata.IConnectionProfile = {
connectionName: 'My Connection', connectionName: 'My Connection',
serverName: 'My Server', serverName: 'My Server',
databaseName: 'My Server', databaseName: 'My Server',
userName: 'My User', userName: 'My User',
password: 'My Pwd', password: 'My Pwd',
authenticationType: 'SqlLogin', authenticationType: 'SqlLogin',
savePassword: false, savePassword: false,
groupFullName: 'My groupName', groupFullName: 'My groupName',
groupId: 'My GroupId', groupId: 'My GroupId',
providerName: 'My Server', providerName: 'My Server',
saveProfile: true, saveProfile: true,
id: 'My Id', id: 'My Id',
options: null options: null
}; };
const mocksource: string = 'source.dacpac'; const mocksource: string = 'source.dacpac';
@@ -34,6 +34,7 @@ const mocktarget: string = 'target.dacpac';
const mockSourceEndpoint: azdata.SchemaCompareEndpointInfo = { const mockSourceEndpoint: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Dacpac, endpointType: azdata.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '', serverName: '',
databaseName: '', databaseName: '',
ownerUri: '', ownerUri: '',
@@ -42,16 +43,18 @@ const mockSourceEndpoint: azdata.SchemaCompareEndpointInfo = {
const mockTargetEndpoint: azdata.SchemaCompareEndpointInfo = { const mockTargetEndpoint: azdata.SchemaCompareEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.Dacpac, endpointType: azdata.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '', serverName: '',
databaseName: '', databaseName: '',
ownerUri: '', ownerUri: '',
packageFilePath: mocktarget packageFilePath: mocktarget
}; };
describe('SchemaCompareDialog.openDialog', function(): void { describe('SchemaCompareDialog.openDialog', function (): void {
it('Should be correct when created.', async function(): Promise<void> { it('Should be correct when created.', async function (): Promise<void> {
let dialog = new SchemaCompareDialog(); let schemaCompareResult = new SchemaCompareResult();
await dialog.openDialog(mockConnectionProfile); let dialog = new SchemaCompareDialog(schemaCompareResult);
await dialog.openDialog();
should(dialog.dialog.title).equal('Schema Compare'); should(dialog.dialog.title).equal('Schema Compare');
should(dialog.dialog.okButton.label).equal('Ok'); should(dialog.dialog.okButton.label).equal('Ok');
@@ -59,17 +62,19 @@ describe('SchemaCompareDialog.openDialog', function(): void {
}); });
}); });
describe('SchemaCompareResult.start', function(): void { describe('SchemaCompareResult.start', function (): void {
it('Should be correct when created.', async function(): Promise<void> { it('Should be correct when created.', async function (): Promise<void> {
let sc = new SchemaCompareTestService(); let sc = new SchemaCompareTestService();
azdata.dataprotocol.registerSchemaCompareServicesProvider(sc); azdata.dataprotocol.registerSchemaCompareServicesProvider(sc);
let result = new SchemaCompareResult(mocksource, mocktarget, mockSourceEndpoint, mockTargetEndpoint); let result = new SchemaCompareResult();
await result.start(null);
let promise = new Promise(resolve => setTimeout(resolve, 3000)); // to ensure comparision result view is initialized let promise = new Promise(resolve => setTimeout(resolve, 3000)); // to ensure comparision result view is initialized
await promise; await promise;
await result.start();
should(result.getComparisonResult() === undefined); should(result.getComparisonResult() === undefined);
result.sourceEndpointInfo = mockSourceEndpoint;
result.targetEndpointInfo = mockTargetEndpoint;
await result.execute(); await result.execute();
should(result.getComparisonResult() !== undefined); should(result.getComparisonResult() !== undefined);

View File

@@ -14,7 +14,13 @@ export class SchemaCompareTestService implements azdata.SchemaCompareServicesPro
} }
schemaCompareGetDefaultOptions(): Thenable<azdata.SchemaCompareOptionsResult> { schemaCompareGetDefaultOptions(): Thenable<azdata.SchemaCompareOptionsResult> {
throw new Error('Method not implemented.'); let result: azdata.SchemaCompareOptionsResult = {
defaultDeploymentOptions: undefined,
success: true,
errorMessage: ''
};
return Promise.resolve(result);
} }
schemaCompareIncludeExcludeNode(operationId: string, diffEntry: azdata.DiffEntry, IncludeRequest: boolean, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.ResultStatus> { schemaCompareIncludeExcludeNode(operationId: string, diffEntry: azdata.DiffEntry, IncludeRequest: boolean, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.ResultStatus> {

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as azdata from 'azdata';
export interface IPackageInfo { export interface IPackageInfo {
name: string; name: string;
@@ -31,4 +32,20 @@ export function getTelemetryErrorType(msg: string): string {
else { else {
return 'Other'; return 'Other';
} }
}
/**
* Return the appropriate endpoint name depending on if the endpoint is a dacpac or a database
* @param endpoint endpoint to get the name of
*/
export function getEndpointName(endpoint: azdata.SchemaCompareEndpointInfo): string {
if (!endpoint) {
return undefined;
}
if (endpoint.endpointType === azdata.SchemaCompareEndpointType.Database) {
return `${endpoint.serverName}.${endpoint.databaseName}`;
} else {
return endpoint.packageFilePath;
}
} }

View File

@@ -1747,6 +1747,7 @@ declare module 'azdata' {
export interface SchemaCompareEndpointInfo { export interface SchemaCompareEndpointInfo {
endpointType: SchemaCompareEndpointType; endpointType: SchemaCompareEndpointType;
packageFilePath: string; packageFilePath: string;
serverDisplayName: string;
serverName: string; serverName: string;
databaseName: string; databaseName: string;
ownerUri: string; ownerUri: string;