mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Feature/schemacompare cancel (#6104)
* Schema compare cancel changes for ADS * adding a missed change * Merge from Master * Updating SqltoolsService version * trying stress with bigger runtime * trying one more stress fix with unique operation ids * refactoring test a bit to ensure stress run works
This commit is contained in:
10
extensions/schema-compare/src/media/stop-inverse.svg
Normal file
10
extensions/schema-compare/src/media/stop-inverse.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#4894FE;}
|
||||
</style>
|
||||
<title>SchemaCompare</title>
|
||||
<rect x="3" y="3" class="st0" width="10" height="10"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 490 B |
10
extensions/schema-compare/src/media/stop.svg
Normal file
10
extensions/schema-compare/src/media/stop.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#015CDA;}
|
||||
</style>
|
||||
<title>SchemaCompare</title>
|
||||
<rect x="3" y="3" class="st0" width="10" height="10"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 490 B |
@@ -12,6 +12,7 @@ import { SchemaCompareOptionsDialog } from './dialogs/schemaCompareOptionsDialog
|
||||
import { Telemetry } from './telemetry';
|
||||
import { getTelemetryErrorType, getEndpointName } from './utils';
|
||||
import { SchemaCompareDialog } from './dialogs/schemaCompareDialog';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
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?');
|
||||
@@ -38,12 +39,14 @@ export class SchemaCompareResult {
|
||||
private sourceTargetFlexLayout: azdata.FlexContainer;
|
||||
private switchButton: azdata.ButtonComponent;
|
||||
private compareButton: azdata.ButtonComponent;
|
||||
private cancelCompareButton: azdata.ButtonComponent;
|
||||
private optionsButton: azdata.ButtonComponent;
|
||||
private generateScriptButton: azdata.ButtonComponent;
|
||||
private applyButton: azdata.ButtonComponent;
|
||||
private selectSourceButton: azdata.ButtonComponent;
|
||||
private selectTargetButton: azdata.ButtonComponent;
|
||||
private SchemaCompareActionMap: Map<Number, string>;
|
||||
private operationId: string;
|
||||
private comparisonResult: azdata.SchemaCompareResult;
|
||||
private sourceNameComponent: azdata.TableComponent;
|
||||
private targetNameComponent: azdata.TableComponent;
|
||||
@@ -112,6 +115,7 @@ export class SchemaCompareResult {
|
||||
|
||||
this.createSwitchButton(view);
|
||||
this.createCompareButton(view);
|
||||
this.createCancelButton(view);
|
||||
this.createGenerateScriptButton(view);
|
||||
this.createApplyButton(view);
|
||||
this.createOptionsButton(view);
|
||||
@@ -121,6 +125,8 @@ export class SchemaCompareResult {
|
||||
let toolBar = view.modelBuilder.toolbarContainer();
|
||||
toolBar.addToolbarItems([{
|
||||
component: this.compareButton
|
||||
}, {
|
||||
component: this.cancelCompareButton
|
||||
}, {
|
||||
component: this.generateScriptButton
|
||||
}, {
|
||||
@@ -247,7 +253,11 @@ export class SchemaCompareResult {
|
||||
}
|
||||
Telemetry.sendTelemetryEvent('SchemaComparisonStarted');
|
||||
const service = await SchemaCompareResult.getService('MSSQL');
|
||||
this.comparisonResult = await service.schemaCompare(this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions);
|
||||
if (!this.operationId) {
|
||||
// create once per page
|
||||
this.operationId = generateGuid();
|
||||
}
|
||||
this.comparisonResult = await service.schemaCompare(this.operationId, this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions);
|
||||
if (!this.comparisonResult || !this.comparisonResult.success) {
|
||||
Telemetry.sendTelemetryEventForError('SchemaComparisonFailed', {
|
||||
'errorType': getTelemetryErrorType(this.comparisonResult.errorMessage),
|
||||
@@ -314,6 +324,7 @@ export class SchemaCompareResult {
|
||||
this.switchButton.enabled = true;
|
||||
this.compareButton.enabled = true;
|
||||
this.optionsButton.enabled = true;
|
||||
this.cancelCompareButton.enabled = false;
|
||||
|
||||
if (this.comparisonResult.differences.length > 0) {
|
||||
this.flexModel.addItem(this.splitView, { CSSStyles: { 'overflow': 'hidden' } });
|
||||
@@ -372,7 +383,7 @@ export class SchemaCompareResult {
|
||||
private saveExcludeState(rowState: azdata.ICheckboxCellActionEventArgs) {
|
||||
if (rowState) {
|
||||
let diff = this.comparisonResult.differences[rowState.row];
|
||||
let key = diff.sourceValue ? diff.sourceValue : diff.targetValue;
|
||||
let key = (diff.sourceValue && diff.sourceValue.length > 0) ? this.createName(diff.sourceValue) : this.createName(diff.targetValue);
|
||||
if (key) {
|
||||
if (!this.sourceTargetSwitched) {
|
||||
this.originalSourceExcludes.delete(key);
|
||||
@@ -403,7 +414,7 @@ export class SchemaCompareResult {
|
||||
}
|
||||
|
||||
private shouldDiffBeIncluded(diff: azdata.DiffEntry): boolean {
|
||||
let key = diff.sourceValue ? diff.sourceValue : diff.targetValue;
|
||||
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;
|
||||
@@ -423,9 +434,9 @@ export class SchemaCompareResult {
|
||||
if (differences) {
|
||||
differences.forEach(difference => {
|
||||
if (difference.differenceType === azdata.SchemaDifferenceType.Object) {
|
||||
if (difference.sourceValue !== null || difference.targetValue !== null) {
|
||||
if ((difference.sourceValue !== null && difference.sourceValue.length > 0) || (difference.targetValue !== null && difference.targetValue.length > 0)) {
|
||||
let state: boolean = this.shouldDiffBeIncluded(difference);
|
||||
data.push([difference.name, difference.sourceValue, state, this.SchemaCompareActionMap[difference.updateAction], difference.targetValue]);
|
||||
data.push([difference.name, this.createName(difference.sourceValue), state, this.SchemaCompareActionMap[difference.updateAction], this.createName(difference.targetValue)]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -434,6 +445,13 @@ export class SchemaCompareResult {
|
||||
return data;
|
||||
}
|
||||
|
||||
private createName(nameParts: string[]): string {
|
||||
if (isNullOrUndefined(nameParts) || nameParts.length === 0) {
|
||||
return '';
|
||||
}
|
||||
return nameParts.join('.');
|
||||
}
|
||||
|
||||
private getFormattedScript(diffEntry: azdata.DiffEntry, getSourceScript: boolean): string {
|
||||
// if there is no entry, the script has to be \n because an empty string shows up as a difference but \n doesn't
|
||||
if ((getSourceScript && diffEntry.sourceScript === null)
|
||||
@@ -497,6 +515,54 @@ export class SchemaCompareResult {
|
||||
});
|
||||
}
|
||||
|
||||
private createCancelButton(view: azdata.ModelView): void {
|
||||
this.cancelCompareButton = view.modelBuilder.button().withProperties({
|
||||
label: localize('schemaCompare.cancelCompareButton', 'Stop'),
|
||||
iconPath: {
|
||||
light: path.join(__dirname, 'media', 'stop.svg'),
|
||||
dark: path.join(__dirname, 'media', 'stop-inverse.svg')
|
||||
},
|
||||
title: localize('schemaCompare.cancelCompareButtonTitle', 'Stop')
|
||||
}).component();
|
||||
|
||||
this.cancelCompareButton.onDidClick(async (click) => {
|
||||
await this.cancelCompare();
|
||||
});
|
||||
}
|
||||
|
||||
private async cancelCompare() {
|
||||
|
||||
Telemetry.sendTelemetryEvent('SchemaCompareCancelStarted', {
|
||||
'startTime:': Date.now().toString(),
|
||||
'operationId': this.operationId
|
||||
});
|
||||
|
||||
// clean the pane
|
||||
this.flexModel.removeItem(this.loader);
|
||||
this.flexModel.removeItem(this.waitText);
|
||||
this.flexModel.addItem(this.startText, { CSSStyles: { 'margin': 'auto' } });
|
||||
this.resetButtons(true);
|
||||
|
||||
// cancel compare
|
||||
if (this.operationId) {
|
||||
const service = await SchemaCompareResult.getService('MSSQL');
|
||||
const result = await service.schemaCompareCancel(this.operationId);
|
||||
|
||||
if (!result || !result.success) {
|
||||
Telemetry.sendTelemetryEvent('SchemaCompareCancelFailed', {
|
||||
'errorType': getTelemetryErrorType(result.errorMessage),
|
||||
'operationId': this.operationId
|
||||
});
|
||||
vscode.window.showErrorMessage(
|
||||
localize('schemaCompare.cancelErrorMessage', "Cancel schema compare failed: '{0}'", (result && result.errorMessage) ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
Telemetry.sendTelemetryEvent('SchemaCompareCancelEnded', {
|
||||
'endTime:': Date.now().toString(),
|
||||
'operationId': this.operationId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private createGenerateScriptButton(view: azdata.ModelView): void {
|
||||
this.generateScriptButton = view.modelBuilder.button().withProperties({
|
||||
label: localize('schemaCompare.generateScriptButton', 'Generate script'),
|
||||
@@ -606,11 +672,13 @@ export class SchemaCompareResult {
|
||||
this.compareButton.enabled = true;
|
||||
this.optionsButton.enabled = true;
|
||||
this.switchButton.enabled = true;
|
||||
this.cancelCompareButton.enabled = false;
|
||||
}
|
||||
else {
|
||||
this.compareButton.enabled = false;
|
||||
this.optionsButton.enabled = false;
|
||||
this.switchButton.enabled = false;
|
||||
this.cancelCompareButton.enabled = true;
|
||||
}
|
||||
this.generateScriptButton.enabled = false;
|
||||
this.applyButton.enabled = false;
|
||||
@@ -713,4 +781,30 @@ export class SchemaCompareResult {
|
||||
let result = await service.schemaCompareGetDefaultOptions();
|
||||
this.deploymentOptions = result.defaultDeploymentOptions;
|
||||
}
|
||||
}
|
||||
|
||||
// Borrowed as is from other extensions
|
||||
// TODO : figure out if any inbuilt alternative is available
|
||||
export function generateGuid(): string {
|
||||
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
|
||||
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
@@ -69,7 +69,7 @@ describe('SchemaCompareResult.start', function (): void {
|
||||
|
||||
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, 5000)); // to ensure comparison result view is initialized
|
||||
await promise;
|
||||
|
||||
should(result.getComparisonResult() === undefined);
|
||||
|
||||
@@ -27,7 +27,7 @@ export class SchemaCompareTestService implements azdata.SchemaCompareServicesPro
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
schemaCompare(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.SchemaCompareResult> {
|
||||
schemaCompare(operationId: string, sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.SchemaCompareResult> {
|
||||
let result: azdata.SchemaCompareResult = {
|
||||
operationId: this.testOperationId,
|
||||
areEqual: true,
|
||||
@@ -39,7 +39,11 @@ export class SchemaCompareTestService implements azdata.SchemaCompareServicesPro
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
schemaCompareGenerateScript(operationId: string, targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.DacFxResult> {
|
||||
schemaCompareGenerateScript(operationId: string, targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
schemaCompareCancel(operationId: string): Thenable<azdata.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user