diff --git a/extensions/integration-tests/src/schemaCompare.test.ts b/extensions/integration-tests/src/schemaCompare.test.ts index c83f121dc0..97dc4a473c 100644 --- a/extensions/integration-tests/src/schemaCompare.test.ts +++ b/extensions/integration-tests/src/schemaCompare.test.ts @@ -39,6 +39,7 @@ if (context.RunTest) { let source: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Dacpac, packageFilePath: dacpac1, + serverDisplayName: '', serverName: '', databaseName: '', ownerUri: '', @@ -46,6 +47,7 @@ if (context.RunTest) { let target: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Dacpac, packageFilePath: dacpac2, + serverDisplayName: '', serverName: '', databaseName: '', ownerUri: '', @@ -86,6 +88,7 @@ if (context.RunTest) { let source: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Database, packageFilePath: '', + serverDisplayName: '', serverName: server.serverName, databaseName: sourceDB, ownerUri: ownerUri, @@ -93,6 +96,7 @@ if (context.RunTest) { let target: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Database, packageFilePath: '', + serverDisplayName: '', serverName: server.serverName, databaseName: targetDB, ownerUri: ownerUri, @@ -137,6 +141,7 @@ if (context.RunTest) { let source: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Dacpac, packageFilePath: dacpac1, + serverDisplayName: '', serverName: '', databaseName: '', ownerUri: ownerUri, @@ -144,6 +149,7 @@ if (context.RunTest) { let target: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Database, packageFilePath: '', + serverDisplayName: '', serverName: server.serverName, databaseName: targetDB, ownerUri: ownerUri, diff --git a/extensions/schema-compare/package.json b/extensions/schema-compare/package.json index aaa1da84db..a1c0776ee1 100644 --- a/extensions/schema-compare/package.json +++ b/extensions/schema-compare/package.json @@ -2,7 +2,7 @@ "name": "schema-compare", "displayName": "%displayName%", "description": "%description%", - "version": "0.3.0", + "version": "0.4.0", "publisher": "Microsoft", "preview": true, "engines": { diff --git a/extensions/schema-compare/src/controllers/mainController.ts b/extensions/schema-compare/src/controllers/mainController.ts index fd884eeca1..00ddd24b50 100644 --- a/extensions/schema-compare/src/controllers/mainController.ts +++ b/extensions/schema-compare/src/controllers/mainController.ts @@ -7,7 +7,7 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { SchemaCompareDialog } from '../dialogs/schemaCompareDialog'; +import { SchemaCompareResult } from '../schemaCompareResult'; /** * The main controller class that initializes the extension @@ -32,7 +32,7 @@ export default class MainController implements vscode.Disposable { } 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 { diff --git a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts index f02ad933af..4708eae0fb 100644 --- a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts +++ b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts @@ -12,6 +12,7 @@ import { SchemaCompareResult } from '../schemaCompareResult'; import { isNullOrUndefined } from 'util'; import { existsSync } from 'fs'; import { Telemetry } from '../telemetry'; +import { getEndpointName } from '../utils'; const localize = nls.loadMessageBundle(); 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 NoActiveConnectionsLabel: string = localize('schemaCompare.noActiveConnectionsText', 'No active connections'); 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; export class SchemaCompareDialog { @@ -51,10 +57,16 @@ export class SchemaCompareDialog { private formBuilder: azdata.FormBuilder; private sourceIsDacpac: boolean; private targetIsDacpac: boolean; - private database: string; private connectionId: string; private sourceDbEditable: 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 { this.schemaCompareTab = azdata.window.createTab(SchemaCompareLabel); @@ -62,22 +74,14 @@ export class SchemaCompareDialog { this.dialog.content = [this.schemaCompareTab]; } - public async openDialog(context: any, dialogName?: string): Promise { - let profile = context ? context.connectionProfile : undefined; - if (profile) { - this.database = profile.databaseName; - this.connectionId = profile.id; - } else { - let connection = await azdata.connection.getCurrentConnection(); - if (connection) { - this.connectionId = connection.connectionId; - this.database = undefined; - } + public async openDialog(): Promise { + // connection to use if schema compare wasn't launched from a database or no previous source/target + let connection = await azdata.connection.getCurrentConnection(); + if (connection) { + this.connectionId = connection.connectionId; } - let event = dialogName ? dialogName : null; - this.dialog = azdata.window.createModelViewDialog(SchemaCompareLabel, event); - + this.dialog = azdata.window.createModelViewDialog(SchemaCompareLabel); this.initializeDialog(); this.dialog.okButton.label = OkButtonText; @@ -91,25 +95,21 @@ export class SchemaCompareDialog { } protected async execute(): Promise { - let sourceName: string; - let targetName: string; - - let sourceEndpointInfo: azdata.SchemaCompareEndpointInfo; if (this.sourceIsDacpac) { - sourceName = this.sourceTextBox.value; - sourceEndpointInfo = { + this.schemaCompareResult.sourceEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Dacpac, + serverDisplayName: '', serverName: '', databaseName: '', ownerUri: '', packageFilePath: this.sourceTextBox.value }; } else { - sourceName = (this.sourceServerDropdown.value as ConnectionDropdownValue).name + '.' + (this.sourceDatabaseDropdown.value).name; let ownerUri = await azdata.connection.getUriForConnection((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId); - sourceEndpointInfo = { + this.schemaCompareResult.sourceEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Database, + serverDisplayName: (this.sourceServerDropdown.value as ConnectionDropdownValue).displayName, serverName: (this.sourceServerDropdown.value as ConnectionDropdownValue).name, databaseName: (this.sourceDatabaseDropdown.value).name, ownerUri: ownerUri, @@ -117,22 +117,21 @@ export class SchemaCompareDialog { }; } - let targetEndpointInfo: azdata.SchemaCompareEndpointInfo; if (this.targetIsDacpac) { - targetName = this.targetTextBox.value; - targetEndpointInfo = { + this.schemaCompareResult.targetEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Dacpac, + serverDisplayName: '', serverName: '', databaseName: '', ownerUri: '', packageFilePath: this.targetTextBox.value }; } else { - targetName = (this.targetServerDropdown.value as ConnectionDropdownValue).name + '.' + (this.targetDatabaseDropdown.value).name; let ownerUri = await azdata.connection.getUriForConnection((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId); - targetEndpointInfo = { + this.schemaCompareResult.targetEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Database, + serverDisplayName: (this.targetServerDropdown.value as ConnectionDropdownValue).displayName, serverName: (this.targetServerDropdown.value as ConnectionDropdownValue).name, databaseName: (this.targetDatabaseDropdown.value).name, ownerUri: ownerUri, @@ -144,8 +143,39 @@ export class SchemaCompareDialog { 'sourceIsDacpac': this.sourceIsDacpac.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 { @@ -154,6 +184,7 @@ export class SchemaCompareDialog { private initializeSchemaCompareTab(): void { this.schemaCompareTab.registerContent(async view => { this.sourceTextBox = view.modelBuilder.inputBox().withProperties({ + value: this.schemaCompareResult.sourceEndpointInfo ? this.schemaCompareResult.sourceEndpointInfo.packageFilePath : '', width: 275 }).component(); @@ -162,6 +193,7 @@ export class SchemaCompareDialog { }); this.targetTextBox = view.modelBuilder.inputBox().withProperties({ + value: this.schemaCompareResult.targetEndpointInfo ? this.schemaCompareResult.targetEndpointInfo.packageFilePath : '', width: 275 }).component(); @@ -185,8 +217,8 @@ export class SchemaCompareDialog { await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId, true); } - this.sourceDacpacComponent = await this.createFileBrowser(view, false); - this.targetDacpacComponent = await this.createFileBrowser(view, true); + this.sourceDacpacComponent = await this.createFileBrowser(view, false, this.schemaCompareResult.sourceEndpointInfo); + this.targetDacpacComponent = await this.createFileBrowser(view, true, this.schemaCompareResult.targetEndpointInfo); let sourceRadioButtons = await this.createSourceRadiobuttons(view); let targetRadioButtons = await this.createTargetRadiobuttons(view); @@ -194,63 +226,60 @@ export class SchemaCompareDialog { this.sourceNoActiveConnectionsText = 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 - if (this.database) { - this.formBuilder = view.modelBuilder.formContainer() - .withFormItems([ - { - title: SourceTitle, - components: [ - sourceRadioButtons, - this.sourceServerComponent, - this.sourceDatabaseComponent - ] - }, { - title: TargetTitle, - components: [ - targetRadioButtons, - this.targetDacpacComponent - ] - } - ], { - horizontal: true, - titleFontSize: titleFontSize - }) - .withLayout({ - width: '100%', - padding: '10px 10px 0 30px' - }); + let sourceComponents = []; + let targetComponents = []; + + // 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) { + sourceComponents = [ + sourceRadioButtons, + this.sourceServerComponent, + this.sourceDatabaseComponent + ]; } else { - this.formBuilder = view.modelBuilder.formContainer() - .withFormItems([ - { - title: SourceTitle, - components: [ - sourceRadioButtons, - this.sourceDacpacComponent, - ] - }, { - title: TargetTitle, - components: [ - targetRadioButtons, - this.targetDacpacComponent - ] - } - ], { - horizontal: true, - titleFontSize: titleFontSize - }) - .withLayout({ - width: '100%', - padding: '10px 10px 0 30px' - }); + sourceComponents = [ + sourceRadioButtons, + this.sourceDacpacComponent, + ]; } + + if (this.schemaCompareResult.targetEndpointInfo && this.schemaCompareResult.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) { + targetComponents = [ + targetRadioButtons, + this.targetServerComponent, + this.targetDatabaseComponent + ]; + } else { + targetComponents = [ + targetRadioButtons, + this.targetDacpacComponent, + ]; + } + + this.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(); await view.initializeModel(formModel); }); } - private async createFileBrowser(view: azdata.ModelView, isTarget: boolean): Promise { + private async createFileBrowser(view: azdata.ModelView, isTarget: boolean, endpoint: azdata.SchemaCompareEndpointInfo): Promise { let currentTextbox = isTarget ? this.targetTextBox : this.sourceTextBox; if (isTarget) { this.targetFileButton = view.modelBuilder.button().withProperties({ @@ -265,13 +294,16 @@ export class SchemaCompareDialog { let currentButton = isTarget ? this.targetFileButton : this.sourceFileButton; 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 defaultUri = endpoint && endpoint.packageFilePath && existsSync(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath; + let fileUris = await vscode.window.showOpenDialog( { canSelectFiles: true, canSelectFolders: false, canSelectMany: false, - defaultUri: vscode.Uri.file(rootPath), + defaultUri: vscode.Uri.file(defaultUri), openLabel: localize('schemaCompare.openFile', 'Open'), filters: { 'dacpac Files': ['dacpac'], @@ -330,7 +362,8 @@ export class SchemaCompareDialog { 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; this.sourceIsDacpac = false; } else { @@ -384,8 +417,15 @@ export class SchemaCompareDialog { this.dialog.okButton.enabled = this.shouldEnableOkayButton(); }); - dacpacRadioButton.checked = true; - this.targetIsDacpac = true; + // if target is currently a db, show it in the server and db dropdowns + 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() .withLayout({ flexFlow: 'column' }) .withItems([dacpacRadioButton, databaseRadioButton] @@ -463,7 +503,7 @@ export class SchemaCompareDialog { protected async populateServerDropdown(isTarget: boolean): Promise { let currentDropdown = isTarget ? this.targetServerDropdown : this.sourceServerDropdown; - let values = await this.getServerValues(); + let values = await this.getServerValues(isTarget); if (values && values.length > 0) { 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(); // This user has no active connections if (!cons || cons.length === 0) { return undefined; } + let endpointInfo = isTarget ? this.schemaCompareResult.targetEndpointInfo : this.schemaCompareResult.sourceEndpointInfo; // reverse list so that most recent connections are first cons.reverse(); @@ -488,10 +529,6 @@ export class SchemaCompareDialog { let values = cons.map(c => { count++; - if (c.connectionId === this.connectionId) { - idx = count; - } - let usr = c.options.user; let srv = c.options.server; @@ -500,10 +537,24 @@ export class SchemaCompareDialog { } 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 { connection: c, displayName: finalName, - name: srv + name: srv, + user: usr }; }); @@ -569,7 +620,7 @@ export class SchemaCompareDialog { let currentDropdown = isTarget ? this.targetDatabaseDropdown : this.sourceDatabaseDropdown; currentDropdown.updateProperties({ values: [], value: null }); - let values = await this.getDatabaseValues(connectionId); + let values = await this.getDatabaseValues(connectionId, isTarget); if (values && values.length > 0) { currentDropdown.updateProperties({ 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 count = -1; let values = (await azdata.connection.listDatabases(connectionId)).map(db => { 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; } diff --git a/extensions/schema-compare/src/schemaCompareResult.ts b/extensions/schema-compare/src/schemaCompareResult.ts index 1da3027059..c59fa1d342 100644 --- a/extensions/schema-compare/src/schemaCompareResult.ts +++ b/extensions/schema-compare/src/schemaCompareResult.ts @@ -10,7 +10,8 @@ import * as os from 'os'; import * as path from 'path'; import { SchemaCompareOptionsDialog } from './dialogs/schemaCompareOptionsDialog'; import { Telemetry } from './telemetry'; -import { getTelemetryErrorType } from './utils'; +import { getTelemetryErrorType, getEndpointName } from './utils'; +import { SchemaCompareDialog } from './dialogs/schemaCompareDialog'; const localize = nls.loadMessageBundle(); const diffEditorTitle = localize('schemaCompare.CompareDetailsTitle', 'Compare Details'); 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 generateScriptButton: azdata.ButtonComponent; private applyButton: azdata.ButtonComponent; + private selectSourceButton: azdata.ButtonComponent; + private selectTargetButton: azdata.ButtonComponent; private SchemaCompareActionMap: Map; private comparisonResult: azdata.SchemaCompareResult; private sourceNameComponent: azdata.TableComponent; @@ -50,18 +53,37 @@ export class SchemaCompareResult { private originalSourceExcludes = new Map(); private originalTargetExcludes = new Map(); 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(); this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Delete] = localize('schemaCompare.deleteAction', 'Delete'); this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Change] = localize('schemaCompare.changeAction', 'Change'); 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.GetDefaultDeploymentOptions(); + } + + public async start(context: any) { + // if schema compare was launched from a db, set that as the source + let profile = context ? 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.differencesTable = view.modelBuilder.table().withProperties({ data: [], height: 300 @@ -93,7 +115,8 @@ export class SchemaCompareResult { this.createGenerateScriptButton(view); this.createApplyButton(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(); toolBar.addToolbarItems([{ @@ -123,12 +146,13 @@ export class SchemaCompareResult { value: localize('schemaCompare.switchLabel', '➔') }).component(); + this.sourceName = this.sourceEndpointInfo ? `${this.sourceEndpointInfo.serverName}.${this.sourceEndpointInfo.databaseName}` : ' '; this.sourceNameComponent = view.modelBuilder.table().withProperties({ columns: [ { - value: sourceName, + value: this.sourceName, headerCssClass: 'no-borders', - toolTip: sourceName + toolTip: this.sourceName }, ] }).component(); @@ -136,18 +160,20 @@ export class SchemaCompareResult { this.targetNameComponent = view.modelBuilder.table().withProperties({ columns: [ { - value: targetName, + value: ' ', headerCssClass: 'no-borders', - toolTip: targetName + toolTip: '' }, ] }).component(); 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' } }); - 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(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.waitText = view.modelBuilder.text().withProperties({ @@ -155,7 +181,7 @@ export class SchemaCompareResult { }).component(); 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(); this.noDifferencesLabel = view.modelBuilder.text().withProperties({ @@ -175,10 +201,33 @@ export class SchemaCompareResult { await view.initializeModel(this.flexModel); }); + + await this.GetDefaultDeploymentOptions(); + this.editor.openEditor(); } - public start(): void { - this.editor.openEditor(); + // update source and target name to display + 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 @@ -525,10 +574,7 @@ export class SchemaCompareResult { }); // disable apply and generate script buttons because the results are no longer valid after applying the changes - this.generateScriptButton.enabled = false; - this.generateScriptButton.title = reCompareToRefeshMessage; - this.applyButton.enabled = false; - this.applyButton.title = reCompareToRefeshMessage; + this.setButtonsForRecompare(); const service = await SchemaCompareResult.getService('MSSQL'); 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; } + 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 { this.switchButton = view.modelBuilder.button().withProperties({ 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 { // generate script and apply can only be enabled if the target is a database if (this.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) { diff --git a/extensions/schema-compare/src/test/schemaCompare.test.ts b/extensions/schema-compare/src/test/schemaCompare.test.ts index cf321ce8f4..41625d92cf 100644 --- a/extensions/schema-compare/src/test/schemaCompare.test.ts +++ b/extensions/schema-compare/src/test/schemaCompare.test.ts @@ -14,19 +14,19 @@ import { SchemaCompareTestService } from './testSchemaCompareService'; // Mock test data const mockConnectionProfile: azdata.IConnectionProfile = { - connectionName: 'My Connection', - serverName: 'My Server', - databaseName: 'My Server', - userName: 'My User', - password: 'My Pwd', - authenticationType: 'SqlLogin', - savePassword: false, - groupFullName: 'My groupName', - groupId: 'My GroupId', - providerName: 'My Server', - saveProfile: true, - id: 'My Id', - options: null + connectionName: 'My Connection', + serverName: 'My Server', + databaseName: 'My Server', + userName: 'My User', + password: 'My Pwd', + authenticationType: 'SqlLogin', + savePassword: false, + groupFullName: 'My groupName', + groupId: 'My GroupId', + providerName: 'My Server', + saveProfile: true, + id: 'My Id', + options: null }; const mocksource: string = 'source.dacpac'; @@ -34,6 +34,7 @@ const mocktarget: string = 'target.dacpac'; const mockSourceEndpoint: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Dacpac, + serverDisplayName: '', serverName: '', databaseName: '', ownerUri: '', @@ -42,16 +43,18 @@ const mockSourceEndpoint: azdata.SchemaCompareEndpointInfo = { const mockTargetEndpoint: azdata.SchemaCompareEndpointInfo = { endpointType: azdata.SchemaCompareEndpointType.Dacpac, + serverDisplayName: '', serverName: '', databaseName: '', ownerUri: '', packageFilePath: mocktarget }; -describe('SchemaCompareDialog.openDialog', function(): void { - it('Should be correct when created.', async function(): Promise { - let dialog = new SchemaCompareDialog(); - await dialog.openDialog(mockConnectionProfile); +describe('SchemaCompareDialog.openDialog', function (): void { + it('Should be correct when created.', async function (): Promise { + let schemaCompareResult = new SchemaCompareResult(); + let dialog = new SchemaCompareDialog(schemaCompareResult); + await dialog.openDialog(); should(dialog.dialog.title).equal('Schema Compare'); should(dialog.dialog.okButton.label).equal('Ok'); @@ -59,17 +62,19 @@ describe('SchemaCompareDialog.openDialog', function(): void { }); }); -describe('SchemaCompareResult.start', function(): void { - it('Should be correct when created.', async function(): Promise { +describe('SchemaCompareResult.start', function (): void { + it('Should be correct when created.', async function (): Promise { let sc = new SchemaCompareTestService(); 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 await promise; - await result.start(); should(result.getComparisonResult() === undefined); + result.sourceEndpointInfo = mockSourceEndpoint; + result.targetEndpointInfo = mockTargetEndpoint; await result.execute(); should(result.getComparisonResult() !== undefined); diff --git a/extensions/schema-compare/src/test/testSchemaCompareService.ts b/extensions/schema-compare/src/test/testSchemaCompareService.ts index 16534aa27a..28b1a88182 100644 --- a/extensions/schema-compare/src/test/testSchemaCompareService.ts +++ b/extensions/schema-compare/src/test/testSchemaCompareService.ts @@ -14,7 +14,13 @@ export class SchemaCompareTestService implements azdata.SchemaCompareServicesPro } schemaCompareGetDefaultOptions(): Thenable { - 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 { diff --git a/extensions/schema-compare/src/utils.ts b/extensions/schema-compare/src/utils.ts index 770a4f4060..a66e8aed7e 100644 --- a/extensions/schema-compare/src/utils.ts +++ b/extensions/schema-compare/src/utils.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as azdata from 'azdata'; export interface IPackageInfo { name: string; @@ -31,4 +32,20 @@ export function getTelemetryErrorType(msg: string): string { else { 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; + } } \ No newline at end of file diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 8f8816a76c..7d20719522 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -1747,6 +1747,7 @@ declare module 'azdata' { export interface SchemaCompareEndpointInfo { endpointType: SchemaCompareEndpointType; packageFilePath: string; + serverDisplayName: string; serverName: string; databaseName: string; ownerUri: string;