diff --git a/extensions/integration-tests/src/schemaCompare.test.ts b/extensions/integration-tests/src/schemaCompare.test.ts index 14e9b560a9..1fc9cac105 100644 --- a/extensions/integration-tests/src/schemaCompare.test.ts +++ b/extensions/integration-tests/src/schemaCompare.test.ts @@ -57,22 +57,24 @@ class SchemaCompareTester { const now = new Date(); const operationId = 'testOperationId_' + now.getTime().toString(); - let source: azdata.SchemaCompareEndpointInfo = { - endpointType: azdata.SchemaCompareEndpointType.Dacpac, - packageFilePath: dacpac1, - serverDisplayName: '', - serverName: '', - databaseName: '', - ownerUri: '', - }; - let target: azdata.SchemaCompareEndpointInfo = { - endpointType: azdata.SchemaCompareEndpointType.Dacpac, - packageFilePath: dacpac2, - serverDisplayName: '', - serverName: '', - databaseName: '', - ownerUri: '', - }; + let source: azdata.SchemaCompareEndpointInfo = { + endpointType: azdata.SchemaCompareEndpointType.Dacpac, + packageFilePath: dacpac1, + serverDisplayName: '', + serverName: '', + databaseName: '', + ownerUri: '', + connectionDetails: undefined + }; + let target: azdata.SchemaCompareEndpointInfo = { + endpointType: azdata.SchemaCompareEndpointType.Dacpac, + packageFilePath: dacpac2, + serverDisplayName: '', + serverName: '', + databaseName: '', + ownerUri: '', + connectionDetails: undefined + }; let schemaCompareResult = await schemaCompareService.schemaCompare(operationId, source, target, azdata.TaskExecutionMode.execute, null); this.assertSchemaCompareResult(schemaCompareResult, operationId); @@ -109,22 +111,24 @@ class SchemaCompareTester { assert(schemaCompareService, 'Schema Compare Service Provider is not available'); - let source: azdata.SchemaCompareEndpointInfo = { - endpointType: azdata.SchemaCompareEndpointType.Database, - packageFilePath: '', - serverDisplayName: '', - serverName: server.serverName, - databaseName: sourceDB, - ownerUri: ownerUri, - }; - let target: azdata.SchemaCompareEndpointInfo = { - endpointType: azdata.SchemaCompareEndpointType.Database, - packageFilePath: '', - serverDisplayName: '', - serverName: server.serverName, - databaseName: targetDB, - ownerUri: ownerUri, - }; + let source: azdata.SchemaCompareEndpointInfo = { + endpointType: azdata.SchemaCompareEndpointType.Database, + packageFilePath: '', + serverDisplayName: '', + serverName: server.serverName, + databaseName: sourceDB, + ownerUri: ownerUri, + connectionDetails: undefined + }; + let target: azdata.SchemaCompareEndpointInfo = { + endpointType: azdata.SchemaCompareEndpointType.Database, + packageFilePath: '', + serverDisplayName: '', + serverName: server.serverName, + databaseName: targetDB, + ownerUri: ownerUri, + connectionDetails: undefined + }; let schemaCompareResult = await schemaCompareService.schemaCompare(operationId, source, target, azdata.TaskExecutionMode.execute, null); this.assertSchemaCompareResult(schemaCompareResult, operationId); @@ -164,22 +168,24 @@ class SchemaCompareTester { assert(result.success === true, 'Deploy database 2 (target) should succeed'); - let source: azdata.SchemaCompareEndpointInfo = { - endpointType: azdata.SchemaCompareEndpointType.Dacpac, - packageFilePath: dacpac1, - serverDisplayName: '', - serverName: '', - databaseName: '', - ownerUri: ownerUri, - }; - let target: azdata.SchemaCompareEndpointInfo = { - endpointType: azdata.SchemaCompareEndpointType.Database, - packageFilePath: '', - serverDisplayName: '', - serverName: server.serverName, - databaseName: targetDB, - ownerUri: ownerUri, - }; + let source: azdata.SchemaCompareEndpointInfo = { + endpointType: azdata.SchemaCompareEndpointType.Dacpac, + packageFilePath: dacpac1, + serverDisplayName: '', + serverName: '', + databaseName: '', + ownerUri: ownerUri, + connectionDetails: undefined + }; + let target: azdata.SchemaCompareEndpointInfo = { + endpointType: azdata.SchemaCompareEndpointType.Database, + packageFilePath: '', + serverDisplayName: '', + serverName: server.serverName, + databaseName: targetDB, + ownerUri: ownerUri, + connectionDetails: undefined + }; assert(schemaCompareService, 'Schema Compare Service Provider is not available'); diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts index ca33e60966..544911723c 100644 --- a/extensions/mssql/src/contracts.ts +++ b/extensions/mssql/src/contracts.ts @@ -467,6 +467,10 @@ export interface SchemaCompareNodeParams { taskExecutionMode: TaskExecutionMode; } +export interface SchemaCompareOpenScmpParams { + filePath: string; +} + export interface SchemaCompareSaveScmpParams { sourceEndpointInfo: azdata.SchemaCompareEndpointInfo; targetEndpointInfo: azdata.SchemaCompareEndpointInfo; @@ -501,6 +505,10 @@ export namespace SchemaCompareIncludeExcludeNodeRequest { export const type = new RequestType('schemaCompare/includeExcludeNode'); } +export namespace SchemaCompareOpenScmpRequest { + export const type = new RequestType('schemaCompare/openScmp'); +} + export namespace SchemaCompareSaveScmpRequest { export const type = new RequestType('schemaCompare/saveScmp'); } diff --git a/extensions/mssql/src/features.ts b/extensions/mssql/src/features.ts index 7f39698c9d..97c807b121 100644 --- a/extensions/mssql/src/features.ts +++ b/extensions/mssql/src/features.ts @@ -146,8 +146,10 @@ export class SchemaCompareServicesFeature extends SqlOpsFeature { private static readonly messageTypes: RPCMessageType[] = [ contracts.SchemaCompareRequest.type, contracts.SchemaCompareGenerateScriptRequest.type, + contracts.SchemaComparePublishChangesRequest.type, contracts.SchemaCompareGetDefaultOptionsRequest.type, contracts.SchemaCompareIncludeExcludeNodeRequest.type, + contracts.SchemaCompareOpenScmpRequest.type, contracts.SchemaCompareSaveScmpRequest.type ]; @@ -233,6 +235,19 @@ export class SchemaCompareServicesFeature extends SqlOpsFeature { ); }; + let schemaCompareOpenScmp = (filePath: string): Thenable => { + let params: contracts.SchemaCompareOpenScmpParams = { filePath: filePath }; + return client.sendRequest(contracts.SchemaCompareOpenScmpRequest.type, params).then( + r => { + return r; + }, + e => { + client.logFailedRequest(contracts.SchemaCompareOpenScmpRequest.type, e); + return Promise.resolve(undefined); + } + ); + }; + let schemaCompareSaveScmp = (sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: azdata.DeploymentOptions, scmpFilePath: string, excludedSourceObjects: azdata.SchemaCompareObjectId[], excludedTargetObjects: azdata.SchemaCompareObjectId[]): Thenable => { let params: contracts.SchemaCompareSaveScmpParams = { sourceEndpointInfo: sourceEndpointInfo, targetEndpointInfo: targetEndpointInfo, taskExecutionMode: taskExecutionMode, deploymentOptions: deploymentOptions, scmpFilePath: scmpFilePath, excludedSourceObjects: excludedSourceObjects, excludedTargetObjects: excludedTargetObjects }; return client.sendRequest(contracts.SchemaCompareSaveScmpRequest.type, params).then( @@ -266,6 +281,7 @@ export class SchemaCompareServicesFeature extends SqlOpsFeature { schemaComparePublishChanges, schemaCompareGetDefaultOptions, schemaCompareIncludeExcludeNode, + schemaCompareOpenScmp, schemaCompareSaveScmp, schemaCompareCancel }); diff --git a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts index 509f1d2bc8..fe7382a39e 100644 --- a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts +++ b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts @@ -102,7 +102,8 @@ export class SchemaCompareDialog { serverName: '', databaseName: '', ownerUri: '', - packageFilePath: this.sourceTextBox.value + packageFilePath: this.sourceTextBox.value, + connectionDetails: undefined }; } else { let ownerUri = await azdata.connection.getUriForConnection((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId); @@ -113,7 +114,8 @@ export class SchemaCompareDialog { serverName: (this.sourceServerDropdown.value as ConnectionDropdownValue).name, databaseName: (this.sourceDatabaseDropdown.value).name, ownerUri: ownerUri, - packageFilePath: '' + packageFilePath: '', + connectionDetails: undefined }; } @@ -124,7 +126,8 @@ export class SchemaCompareDialog { serverName: '', databaseName: '', ownerUri: '', - packageFilePath: this.targetTextBox.value + packageFilePath: this.targetTextBox.value, + connectionDetails: undefined }; } else { let ownerUri = await azdata.connection.getUriForConnection((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId); @@ -135,7 +138,8 @@ export class SchemaCompareDialog { serverName: (this.targetServerDropdown.value as ConnectionDropdownValue).name, databaseName: (this.targetDatabaseDropdown.value).name, ownerUri: ownerUri, - packageFilePath: '' + packageFilePath: '', + connectionDetails: undefined }; } @@ -541,7 +545,7 @@ export class SchemaCompareDialog { console.error('finalname: ' + finalName + ' endpointname: ' + endpointInfo.serverDisplayName); } // use previously selected server or current connection if there is one - if (endpointInfo && endpointInfo.serverName !== null + if (endpointInfo && !isNullOrUndefined(endpointInfo.serverName) && !isNullOrUndefined(endpointInfo.serverDisplayName) && c.options.server.toLowerCase() === endpointInfo.serverName.toLowerCase() && finalName.toLowerCase() === endpointInfo.serverDisplayName.toLowerCase()) { idx = count; diff --git a/extensions/schema-compare/src/media/open-scmp-inverse.svg b/extensions/schema-compare/src/media/open-scmp-inverse.svg new file mode 100644 index 0000000000..b39a92654a --- /dev/null +++ b/extensions/schema-compare/src/media/open-scmp-inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/schema-compare/src/media/open-scmp.svg b/extensions/schema-compare/src/media/open-scmp.svg new file mode 100644 index 0000000000..6fc5ee35b4 --- /dev/null +++ b/extensions/schema-compare/src/media/open-scmp.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/schema-compare/src/schemaCompareResult.ts b/extensions/schema-compare/src/schemaCompareResult.ts index a5fccfcce9..f14a73b29e 100644 --- a/extensions/schema-compare/src/schemaCompareResult.ts +++ b/extensions/schema-compare/src/schemaCompareResult.ts @@ -10,7 +10,7 @@ import * as os from 'os'; import * as path from 'path'; import { SchemaCompareOptionsDialog } from './dialogs/schemaCompareOptionsDialog'; import { Telemetry } from './telemetry'; -import { getTelemetryErrorType, getEndpointName } from './utils'; +import { getTelemetryErrorType, getEndpointName, verifyConnectionAndGetOwnerUri } from './utils'; import { SchemaCompareDialog } from './dialogs/schemaCompareDialog'; import { isNullOrUndefined } from 'util'; const localize = nls.loadMessageBundle(); @@ -26,6 +26,11 @@ const applyNoChangesMessage = localize('schemaCompare.applyNoChanges', 'No chang // TODO : In future icon should be decided based on language id (scmp) and not resource name const schemaCompareResourceName = 'Schema Compare'; +enum ResetButtonState { + noSourceTarget, + beforeCompareStart, + comparing, +} export class SchemaCompareResult { private differencesTable: azdata.TableComponent; @@ -44,6 +49,7 @@ export class SchemaCompareResult { private optionsButton: azdata.ButtonComponent; private generateScriptButton: azdata.ButtonComponent; private applyButton: azdata.ButtonComponent; + private openScmpButton: azdata.ButtonComponent; private selectSourceButton: azdata.ButtonComponent; private selectTargetButton: azdata.ButtonComponent; private saveScmpButton: azdata.ButtonComponent; @@ -60,6 +66,8 @@ export class SchemaCompareResult { private sourceTargetSwitched = false; private sourceName: string; private targetName: string; + private scmpSourceExcludes: azdata.SchemaCompareObjectId[]; + private scmpTargetExcludes: azdata.SchemaCompareObjectId[]; public sourceEndpointInfo: azdata.SchemaCompareEndpointInfo; public targetEndpointInfo: azdata.SchemaCompareEndpointInfo; @@ -84,7 +92,8 @@ export class SchemaCompareResult { serverName: profile.serverName, databaseName: profile.databaseName, ownerUri: ownerUri, - packageFilePath: '' + packageFilePath: '', + connectionDetails: undefined }; } @@ -121,9 +130,10 @@ export class SchemaCompareResult { this.createGenerateScriptButton(view); this.createApplyButton(view); this.createOptionsButton(view); - this.createSourceAndTargetButtons(view); + this.createOpenScmpButton(view); this.createSaveScmpButton(view); - this.resetButtons(false); // disable buttons because source and target aren't both selected yet + this.createSourceAndTargetButtons(view); + this.resetButtons(ResetButtonState.noSourceTarget); let toolBar = view.modelBuilder.toolbarContainer(); toolBar.addToolbarItems([{ @@ -140,6 +150,8 @@ export class SchemaCompareResult { }, { component: this.switchButton, toolbarSeparatorAfter: true + }, { + component: this.openScmpButton }, { component: this.saveScmpButton }]); @@ -158,7 +170,8 @@ export class SchemaCompareResult { value: localize('schemaCompare.switchLabel', '➔') }).component(); - this.sourceName = this.sourceEndpointInfo ? `${this.sourceEndpointInfo.serverName}.${this.sourceEndpointInfo.databaseName}` : ' '; + this.sourceName = getEndpointName(this.sourceEndpointInfo); + this.targetName = ' '; this.sourceNameComponent = view.modelBuilder.table().withProperties({ columns: [ { @@ -172,9 +185,9 @@ export class SchemaCompareResult { this.targetNameComponent = view.modelBuilder.table().withProperties({ columns: [ { - value: ' ', + value: this.targetName, headerCssClass: 'no-borders', - toolTip: '' + toolTip: this.targetName }, ] }).component(); @@ -239,7 +252,7 @@ export class SchemaCompareResult { ]); // reset buttons to before comparison state - this.resetButtons(true); + this.resetButtons(ResetButtonState.beforeCompareStart); } // only for test @@ -330,6 +343,7 @@ export class SchemaCompareResult { this.switchButton.enabled = true; this.compareButton.enabled = true; this.optionsButton.enabled = true; + this.openScmpButton.enabled = true; this.cancelCompareButton.enabled = false; if (this.comparisonResult.differences.length > 0) { @@ -393,6 +407,8 @@ export class SchemaCompareResult { if (key) { if (!this.sourceTargetSwitched) { this.originalSourceExcludes.delete(key); + this.removeExcludeEntry(this.scmpSourceExcludes, key); + if (!rowState.checked) { this.originalSourceExcludes.set(key, diff); if (this.originalSourceExcludes.size === this.comparisonResult.differences.length) { @@ -405,6 +421,8 @@ export class SchemaCompareResult { } else { this.originalTargetExcludes.delete(key); + this.removeExcludeEntry(this.scmpTargetExcludes, key); + if (!rowState.checked) { this.originalTargetExcludes.set(key, diff); if (this.originalTargetExcludes.size === this.comparisonResult.differences.length) { @@ -422,12 +440,14 @@ export class SchemaCompareResult { private shouldDiffBeIncluded(diff: azdata.DiffEntry): boolean { let key = (diff.sourceValue && diff.sourceValue.length > 0) ? this.createName(diff.sourceValue) : this.createName(diff.targetValue); if (key) { - if (this.sourceTargetSwitched === true && this.originalTargetExcludes.has(key)) { - this.originalTargetExcludes[key] = diff; + if (this.sourceTargetSwitched === true + && (this.originalTargetExcludes.has(key) || this.hasExcludeEntry(this.scmpTargetExcludes, key))) { + this.originalTargetExcludes.set(key, diff); return false; } - if (this.sourceTargetSwitched === false && this.originalSourceExcludes.has(key)) { - this.originalSourceExcludes[key] = diff; + if (this.sourceTargetSwitched === false + && (this.originalSourceExcludes.has(key) || this.hasExcludeEntry(this.scmpSourceExcludes, key))) { + this.originalSourceExcludes.set(key, diff); return false; } return true; @@ -435,6 +455,23 @@ export class SchemaCompareResult { return true; } + private hasExcludeEntry(collection: azdata.SchemaCompareObjectId[], entryName: string): boolean { + let found = false; + if (collection) { + const index = collection.findIndex(e => this.createName(e.nameParts) === entryName); + found = index !== -1; + } + return found; + } + + private removeExcludeEntry(collection: azdata.SchemaCompareObjectId[], entryName: string) { + if (collection) { + console.error('removing ' + entryName); + const index = collection.findIndex(e => this.createName(e.nameParts) === entryName); + collection.splice(index, 1); + } + } + private getAllDifferences(differences: azdata.DiffEntry[]): string[][] { let data = []; if (differences) { @@ -502,7 +539,7 @@ export class SchemaCompareResult { if (this.tablelistenersToDispose) { this.tablelistenersToDispose.forEach(x => x.dispose()); } - this.resetButtons(false); + this.resetButtons(ResetButtonState.comparing); this.execute(); } @@ -547,7 +584,7 @@ export class SchemaCompareResult { this.flexModel.removeItem(this.loader); this.flexModel.removeItem(this.waitText); this.flexModel.addItem(this.startText, { CSSStyles: { 'margin': 'auto' } }); - this.resetButtons(true); + this.resetButtons(ResetButtonState.beforeCompareStart); // cancel compare if (this.operationId) { @@ -611,16 +648,12 @@ export class SchemaCompareResult { }).component(); this.optionsButton.onDidClick(async (click) => { - Telemetry.sendTelemetryEvent('SchemaCompareOptionsOpened', { - 'operationId': this.comparisonResult.operationId - }); - //restore options from last time - if (this.schemaCompareOptionDialog && this.schemaCompareOptionDialog.deploymentOptions) { - this.deploymentOptions = this.schemaCompareOptionDialog.deploymentOptions; - } + Telemetry.sendTelemetryEvent('SchemaCompareOptionsOpened'); + // create fresh every time this.schemaCompareOptionDialog = new SchemaCompareOptionsDialog(this.deploymentOptions, this); await this.schemaCompareOptionDialog.openDialog(); + this.deploymentOptions = this.schemaCompareOptionDialog.deploymentOptions; }); } @@ -673,20 +706,35 @@ export class SchemaCompareResult { }); } - private resetButtons(beforeCompareStart: boolean): void { - if (beforeCompareStart) { - this.compareButton.enabled = true; - this.optionsButton.enabled = true; - this.switchButton.enabled = true; - this.cancelCompareButton.enabled = false; - this.saveScmpButton.enabled = true; - } - else { - this.compareButton.enabled = false; - this.optionsButton.enabled = false; - this.switchButton.enabled = false; - this.cancelCompareButton.enabled = true; + private resetButtons(resetButtonState: ResetButtonState): void { + switch (resetButtonState) { + case (ResetButtonState.noSourceTarget): { + this.compareButton.enabled = false; + this.optionsButton.enabled = false; + this.switchButton.enabled = this.sourceEndpointInfo ? true : false; // allows switching if the source is set + this.openScmpButton.enabled = true; + this.cancelCompareButton.enabled = false; + break; + } + case (ResetButtonState.beforeCompareStart): { + this.compareButton.enabled = true; + this.optionsButton.enabled = true; + this.switchButton.enabled = true; + this.openScmpButton.enabled = true; + this.saveScmpButton.enabled = true; + this.cancelCompareButton.enabled = false; + break; + } + case (ResetButtonState.comparing): { + this.compareButton.enabled = false; + this.optionsButton.enabled = false; + this.switchButton.enabled = false; + this.openScmpButton.enabled = false; + this.cancelCompareButton.enabled = true; + break; + } } + this.generateScriptButton.enabled = false; this.applyButton.enabled = false; this.generateScriptButton.title = generateScriptEnabledMessage; @@ -700,6 +748,14 @@ export class SchemaCompareResult { this.applyButton.title = reCompareToRefeshMessage; } + // reset state afer loading an scmp + private resetForNewCompare(): void { + this.resetButtons(ResetButtonState.beforeCompareStart); + this.flexModel.removeItem(this.splitView); + this.flexModel.removeItem(this.noDifferencesLabel); + this.flexModel.addItem(this.startText, { CSSStyles: { 'margin': 'auto' } }); + } + private createSwitchButton(view: azdata.ModelView): void { this.switchButton = view.modelBuilder.button().withProperties({ label: localize('schemaCompare.switchDirectionButton', 'Switch direction'), @@ -739,7 +795,11 @@ export class SchemaCompareResult { // remember that source target have been toggled this.sourceTargetSwitched = this.sourceTargetSwitched ? false : true; - this.startCompare(); + + // only compare if both source and target are set + if (this.sourceEndpointInfo && this.targetEndpointInfo) { + this.startCompare(); + } }); } @@ -767,6 +827,85 @@ export class SchemaCompareResult { }); } + private createOpenScmpButton(view: azdata.ModelView) { + this.openScmpButton = view.modelBuilder.button().withProperties({ + label: localize('schemaCompare.openScmpButton', 'Open .scmp file'), + iconPath: { + light: path.join(__dirname, 'media', 'open-scmp.svg'), + dark: path.join(__dirname, 'media', 'open-scmp-inverse.svg') + }, + title: localize('schemaCompare.openScmpButtonTitle', 'Load source, target, and options saved in an .scmp file') + }).component(); + + this.openScmpButton.onDidClick(async (click) => { + Telemetry.sendTelemetryEvent('SchemaCompareOpenScmpStarted'); + const rootPath = vscode.workspace.rootPath ? vscode.workspace.rootPath : os.homedir(); + let fileUris = await vscode.window.showOpenDialog( + { + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + defaultUri: vscode.Uri.file(rootPath), + openLabel: localize('schemaCompare.openFile', 'Open'), + filters: { + 'scmp Files': ['scmp'], + } + } + ); + + if (!fileUris || fileUris.length === 0) { + return; + } + + let fileUri = fileUris[0]; + const service = await SchemaCompareResult.getService('MSSQL'); + let startTime = Date.now(); + const result = await service.schemaCompareOpenScmp(fileUri.fsPath); + if (!result || !result.success) { + Telemetry.sendTelemetryEvent('SchemaCompareOpenScmpFailed', { + 'errorType': getTelemetryErrorType(result.errorMessage) + }); + vscode.window.showErrorMessage( + localize('schemaCompare.openScmpErrorMessage', "Open scmp failed: '{0}'", (result && result.errorMessage) ? result.errorMessage : 'Unknown')); + return; + } + + if (result.sourceEndpointInfo && result.sourceEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) { + // only set endpoint info if able to connect to the database + const ownerUri = await verifyConnectionAndGetOwnerUri(result.sourceEndpointInfo); + if (ownerUri) { + this.sourceEndpointInfo = result.sourceEndpointInfo; + this.sourceEndpointInfo.ownerUri = ownerUri; + } + } else { + this.sourceEndpointInfo = result.sourceEndpointInfo; + } + + if (result.targetEndpointInfo && result.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.Database) { + const ownerUri = await verifyConnectionAndGetOwnerUri(result.targetEndpointInfo); + if (ownerUri) { + this.targetEndpointInfo = result.targetEndpointInfo; + this.targetEndpointInfo.ownerUri = ownerUri; + } + } else { + this.targetEndpointInfo = result.targetEndpointInfo; + } + + this.updateSourceAndTarget(); + this.deploymentOptions = result.deploymentOptions; + this.scmpSourceExcludes = result.excludedSourceElements; + this.scmpTargetExcludes = result.excludedTargetElements; + this.sourceTargetSwitched = result.originalTargetName !== this.targetEndpointInfo.databaseName; + + // clear out any old results + this.resetForNewCompare(); + + Telemetry.sendTelemetryEvent('SchemaCompareOpenScmpEnded', { + 'elapsedTime:': (Date.now() - startTime).toString() + }); + }); + } + private createSaveScmpButton(view: azdata.ModelView): void { this.saveScmpButton = view.modelBuilder.button().withProperties({ label: localize('schemaCompare.saveScmpButton', 'Save .scmp file'), @@ -812,7 +951,7 @@ export class SchemaCompareResult { } Telemetry.sendTelemetryEvent('SchemaCompareSaveScmpEnded', { - 'totalSaveTime:': (Date.now() - startTime).toString(), + 'elapsedTime:': (Date.now() - startTime).toString(), 'operationId': this.comparisonResult.operationId }); }); diff --git a/extensions/schema-compare/src/test/schemaCompare.test.ts b/extensions/schema-compare/src/test/schemaCompare.test.ts index 59b43004f5..bab2706d50 100644 --- a/extensions/schema-compare/src/test/schemaCompare.test.ts +++ b/extensions/schema-compare/src/test/schemaCompare.test.ts @@ -38,7 +38,8 @@ const mockSourceEndpoint: azdata.SchemaCompareEndpointInfo = { serverName: '', databaseName: '', ownerUri: '', - packageFilePath: mocksource + packageFilePath: mocksource, + connectionDetails: undefined }; const mockTargetEndpoint: azdata.SchemaCompareEndpointInfo = { @@ -47,7 +48,8 @@ const mockTargetEndpoint: azdata.SchemaCompareEndpointInfo = { serverName: '', databaseName: '', ownerUri: '', - packageFilePath: mocktarget + packageFilePath: mocktarget, + connectionDetails: undefined }; describe('SchemaCompareDialog.openDialog', function (): void { diff --git a/extensions/schema-compare/src/test/testSchemaCompareService.ts b/extensions/schema-compare/src/test/testSchemaCompareService.ts index bd0cc0c1d2..5669465a4d 100644 --- a/extensions/schema-compare/src/test/testSchemaCompareService.ts +++ b/extensions/schema-compare/src/test/testSchemaCompareService.ts @@ -27,6 +27,10 @@ export class SchemaCompareTestService implements azdata.SchemaCompareServicesPro throw new Error('Method not implemented.'); } + schemaCompareOpenScmp(filePath: string): Thenable { + throw new Error('Method not implemented.'); + } + schemaCompareSaveScmp(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: azdata.DeploymentOptions, scmpFilePath: string, excludedSourceObjects: azdata.SchemaCompareObjectId[], excludedTargetObjects: azdata.SchemaCompareObjectId[]): Thenable { throw new Error('Method not implemented.'); diff --git a/extensions/schema-compare/src/utils.ts b/extensions/schema-compare/src/utils.ts index a66e8aed7e..734f218049 100644 --- a/extensions/schema-compare/src/utils.ts +++ b/extensions/schema-compare/src/utils.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; import * as azdata from 'azdata'; +import * as vscode from 'vscode'; export interface IPackageInfo { name: string; @@ -40,12 +41,46 @@ export function getTelemetryErrorType(msg: string): string { */ export function getEndpointName(endpoint: azdata.SchemaCompareEndpointInfo): string { if (!endpoint) { - return undefined; + return ' '; } if (endpoint.endpointType === azdata.SchemaCompareEndpointType.Database) { + if (!endpoint.serverName && endpoint.connectionDetails) { + endpoint.serverName = endpoint.connectionDetails['serverName']; + } return `${endpoint.serverName}.${endpoint.databaseName}`; } else { return endpoint.packageFilePath; } +} + +function connectionInfoToConnectionProfile(details: azdata.ConnectionInfo): azdata.IConnectionProfile { + return { + serverName: details['serverName'], + databaseName: details['databaseName'], + authenticationType: details['authenticationType'], + providerName: 'MSSQL', + connectionName: '', + userName: details['userName'], + password: details['password'], + savePassword: false, + groupFullName: undefined, + saveProfile: true, + id: undefined, + groupId: undefined, + options: details['options'] + }; +} + +export async function verifyConnectionAndGetOwnerUri(endpoint: azdata.SchemaCompareEndpointInfo): Promise { + if (endpoint.endpointType === azdata.SchemaCompareEndpointType.Database && endpoint.connectionDetails) { + let connection = await azdata.connection.connect(connectionInfoToConnectionProfile(endpoint.connectionDetails), false, false); + + // show error message if the can't connect to the database + if (connection.errorMessage) { + vscode.window.showErrorMessage(connection.errorMessage); + } + return await azdata.connection.getUriForConnection(connection.connectionId); + } + return undefined; } \ No newline at end of file diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 05d7d91fa4..7410fa9576 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -1779,6 +1779,7 @@ declare module 'azdata' { serverName: string; databaseName: string; ownerUri: string; + connectionDetails: ConnectionInfo; } export interface SchemaCompareObjectId { @@ -1940,12 +1941,28 @@ declare module 'azdata' { ServerTriggers = 65 } + export interface SchemaCompareObjectId { + nameParts: string[]; + sqlObjectType: string; + } + + export interface SchemaCompareOpenScmpResult extends ResultStatus { + sourceEndpointInfo: SchemaCompareEndpointInfo; + targetEndpointInfo: SchemaCompareEndpointInfo; + originalTargetName: string; + originalConnectionString: string; + deploymentOptions: DeploymentOptions; + excludedSourceElements: SchemaCompareObjectId[]; + excludedTargetElements: SchemaCompareObjectId[]; + } + export interface SchemaCompareServicesProvider extends DataProvider { schemaCompare(operationId: string, sourceEndpointInfo: SchemaCompareEndpointInfo, targetEndpointInfo: SchemaCompareEndpointInfo, taskExecutionMode: TaskExecutionMode, deploymentOptions: DeploymentOptions): Thenable; schemaCompareGenerateScript(operationId: string, targetServerName: string, targetDatabaseName: string, taskExecutionMode: TaskExecutionMode): Thenable; schemaComparePublishChanges(operationId: string, targetServerName: string, targetDatabaseName: string, taskExecutionMode: TaskExecutionMode): Thenable; schemaCompareGetDefaultOptions(): Thenable; schemaCompareIncludeExcludeNode(operationId: string, diffEntry: DiffEntry, IncludeRequest: boolean, taskExecutionMode: TaskExecutionMode): Thenable; + schemaCompareOpenScmp(filePath: string): Thenable; schemaCompareSaveScmp(sourceEndpointInfo: SchemaCompareEndpointInfo, targetEndpointInfo: SchemaCompareEndpointInfo, taskExecutionMode: TaskExecutionMode, deploymentOptions: DeploymentOptions, scmpFilePath: string, excludedSourceObjects: SchemaCompareObjectId[], excludedTargetObjects: SchemaCompareObjectId[]): Thenable; schemaCompareCancel(operationId: string): Thenable; } diff --git a/src/sql/platform/schemaCompare/common/schemaCompareService.ts b/src/sql/platform/schemaCompare/common/schemaCompareService.ts index 1e0646211e..dd7214e678 100644 --- a/src/sql/platform/schemaCompare/common/schemaCompareService.ts +++ b/src/sql/platform/schemaCompare/common/schemaCompareService.ts @@ -20,6 +20,7 @@ export interface ISchemaCompareService { schemaComparePublishChanges(operationId: string, targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): void; schemaCompareGetDefaultOptions(): void; schemaCompareIncludeExcludeNode(operationId: string, diffEntry: azdata.DiffEntry, includeRequest: boolean, taskExecutionMode: azdata.TaskExecutionMode): void; + schemaCompareOpenScmp(filePath: string): void; schemaCompareSaveScmp(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: azdata.DeploymentOptions, scmpFilePath: string, excludedSourceObjects: azdata.SchemaCompareObjectId[], excludedTargetObjects: azdata.SchemaCompareObjectId[]); schemaCompareCancel(operationId: string): void; } @@ -64,6 +65,12 @@ export class SchemaCompareService implements ISchemaCompareService { }); } + schemaCompareOpenScmp(filePath: string): Thenable { + return this._runAction('', (runner) => { + return runner.schemaCompareOpenScmp(filePath); + }); + } + schemaCompareSaveScmp(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: azdata.DeploymentOptions, scmpFilePath: string, excludedSourceObjects: azdata.SchemaCompareObjectId[], excludedTargetObjects: azdata.SchemaCompareObjectId[]) { return this._runAction('', (runner) => { return runner.schemaCompareSaveScmp(sourceEndpointInfo, targetEndpointInfo, taskExecutionMode, deploymentOptions, scmpFilePath, excludedSourceObjects, excludedTargetObjects); diff --git a/src/sql/workbench/api/node/mainThreadDataProtocol.ts b/src/sql/workbench/api/node/mainThreadDataProtocol.ts index ef202d46eb..4e3480f290 100644 --- a/src/sql/workbench/api/node/mainThreadDataProtocol.ts +++ b/src/sql/workbench/api/node/mainThreadDataProtocol.ts @@ -485,6 +485,9 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape { schemaCompareIncludeExcludeNode(operationId: string, diffEntry: azdata.DiffEntry, includeRequest: boolean, taskExecutionMode: azdata.TaskExecutionMode): Thenable { return self._proxy.$schemaCompareIncludeExcludeNode(handle, operationId, diffEntry, includeRequest, taskExecutionMode); }, + schemaCompareOpenScmp(filePath: string): Thenable { + return self._proxy.$schemaCompareOpenScmp(handle, filePath); + }, schemaCompareSaveScmp(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: azdata.DeploymentOptions, scmpFilePath: string, excludedSourceObjects: azdata.SchemaCompareObjectId[], excludedTargetObjects: azdata.SchemaCompareObjectId[]): Thenable { return self._proxy.$schemaCompareSaveScmp(handle, sourceEndpointInfo, targetEndpointInfo, taskExecutionMode, deploymentOptions, scmpFilePath, excludedSourceObjects, excludedTargetObjects); }, diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 29f519fb87..6e5ee38fc8 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -488,6 +488,12 @@ export abstract class ExtHostDataProtocolShape { */ $schemaCompareIncludeExcludeNode(handle: number, operationId: string, diffEntry: azdata.DiffEntry, includeRequest: boolean, taskExecutionMode: azdata.TaskExecutionMode): Thenable { throw ni(); } + /** + * Schema compare open scmp + */ + $schemaCompareOpenScmp(handle: number, filePath: string): Thenable { throw ni(); } + + /** * Schema compare save scmp */