Add IR Migration configuration Validation to SQL Migration extension (#21386)

* re-factor and consolidate wizard pages

* validation WIP 11/10

* validate ir dialog

* navigation fixes

* bump version to 1.2.0

* add resource strings and fix navigatin issue

* map validation state to resource string clean up

* address review comments

* fix typos, address review comments

* address review feedback, readability

* fix res string, validation check, col width

* bug fixes, nav, sqldb migration

* fix nav/refresh/visibility issues

* fix nav issues, cancel pending validation items

* update error text / position

* fix localization bug
This commit is contained in:
brian-harris
2022-12-16 14:52:24 -08:00
committed by GitHub
parent 754d70d654
commit 2e240729af
29 changed files with 1993 additions and 692 deletions

View File

@@ -72,14 +72,16 @@ export class AssessmentResultsDialog {
this.dialog = azdata.window.createModelViewDialog(this.title, 'AssessmentResults', 'wide');
this.dialog.okButton.label = AssessmentResultsDialog.SelectButtonText;
this.dialog.okButton.position = 'left';
this._disposables.push(this.dialog.okButton.onClick(async () => await this.execute()));
this.dialog.cancelButton.label = AssessmentResultsDialog.CancelButtonText;
this.dialog.cancelButton.position = 'left';
this._disposables.push(this.dialog.cancelButton.onClick(async () => await this.cancel()));
this._saveButton = azdata.window.createButton(
constants.SAVE_ASSESSMENT_REPORT,
'left');
'right');
this._disposables.push(
this._saveButton.onClick(async () => {
const folder = await utils.promptUserForFolder();

View File

@@ -74,7 +74,10 @@ export class SavedAssessmentDialog {
this._isOpen = true;
this.dialog = azdata.window.createModelViewDialog(constants.SAVED_ASSESSMENT_RESULT, constants.SAVED_ASSESSMENT_RESULT, '60%');
this.dialog.okButton.label = SavedAssessmentDialog.OkButtonText;
this.dialog.okButton.position = 'left';
this.dialog.cancelButton.label = SavedAssessmentDialog.CancelButtonText;
this.dialog.cancelButton.position = 'left';
const dialogSetupPromises: Thenable<void>[] = [];
dialogSetupPromises.push(this.initializeDialog(this.dialog));
azdata.window.openDialog(this.dialog);

View File

@@ -187,6 +187,7 @@ export class CreateResourceGroupDialog {
});
});
this._dialogObject.okButton.label = constants.APPLY;
this._dialogObject.okButton.position = 'left';
this._dialogObject.content = [tab];
azdata.window.openDialog(this._dialogObject);

View File

@@ -58,6 +58,9 @@ export class CreateSqlMigrationServiceDialog {
this._model = migrationStateModel;
this._resourceGroupPreset = resourceGroupPreset;
this._dialogObject = azdata.window.createModelViewDialog(constants.CREATE_MIGRATION_SERVICE_TITLE, 'MigrationServiceDialog', 'medium');
this._dialogObject.okButton.position = 'left';
this._dialogObject.cancelButton.position = 'left';
let tab = azdata.window.createTab('');
this._dialogObject.registerCloseValidator(async () => {
return true;

View File

@@ -78,12 +78,12 @@ export class SelectMigrationServiceDialog {
});
this._dialog.okButton.label = constants.MIGRATION_SERVICE_SELECT_APPLY_LABEL;
this._dialog.okButton.position = 'right';
this._dialog.cancelButton.position = 'right';
this._dialog.okButton.position = 'left';
this._dialog.cancelButton.position = 'left';
this._deleteButton = azdata.window.createButton(
constants.MIGRATION_SERVICE_CLEAR,
'left');
'right');
this._disposables.push(
this._deleteButton.onClick(async (value) => {
await MigrationLocalStorage.saveMigrationServiceContext({}, this.onServiceContextChanged);

View File

@@ -290,10 +290,13 @@ export class GetAzureRecommendationDialog {
'narrow');
this.dialog.okButton.label = GetAzureRecommendationDialog.StartButtonText;
this.dialog.okButton.position = 'left';
this._disposables.push(
this.dialog.okButton.onClick(
async () => await this.execute()));
this.dialog.cancelButton.position = 'left';
this._disposables.push(
this.dialog.cancelButton.onClick(
() => this._isOpen = false));

View File

@@ -270,6 +270,8 @@ export class SkuEditParametersDialog {
'narrow');
this.dialog.okButton.label = SkuEditParametersDialog.UpdateButtonText;
this.dialog.okButton.position = 'left';
this.dialog.cancelButton.position = 'left';
this._disposables.push(
this.dialog.okButton.onClick(
async () => await this.execute()));

View File

@@ -492,6 +492,7 @@ export class SkuRecommendationResultsDialog {
this.dialog = azdata.window.createModelViewDialog(this.title!, 'SkuRecommendationResultsDialog', 'medium');
this.dialog.okButton.label = SkuRecommendationResultsDialog.OpenButtonText;
this.dialog.okButton.position = 'left';
this._disposables.push(this.dialog.okButton.onClick(async () => await this.execute()));
this.dialog.cancelButton.hidden = true;
@@ -501,7 +502,7 @@ export class SkuRecommendationResultsDialog {
this._saveButton = azdata.window.createButton(
constants.SAVE_RECOMMENDATION_REPORT,
'left');
'right');
this._disposables.push(
this._saveButton.onClick(async () => {
const folder = await utils.promptUserForFolder();

View File

@@ -55,6 +55,8 @@ export class SqlMigrationServiceDetailsDialog {
this._dialog.okButton.label = constants.SQL_MIGRATION_SERVICE_DETAILS_BUTTON_LABEL;
this._dialog.okButton.focused = true;
this._dialog.okButton.position = 'left';
this._dialog.cancelButton.hidden = true;
azdata.window.openDialog(this._dialog);
}

View File

@@ -185,11 +185,13 @@ export class TableMigrationSelectionDialog {
600);
this._dialog.okButton.label = constants.TABLE_SELECTION_UPDATE_BUTTON;
this._dialog.okButton.position = 'left';
this._disposables.push(
this._dialog.okButton.onClick(
async () => this._save()));
this._dialog.cancelButton.label = constants.TABLE_SELECTION_CANCEL_BUTTON;
this._dialog.cancelButton.position = 'left';
this._disposables.push(
this._dialog.cancelButton.onClick(
async () => this._isOpen = false));

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType } from '../../models/stateMachine';
import { MigrationMode, MigrationStateModel, NetworkContainerType } from '../../models/stateMachine';
import * as constants from '../../constants/strings';
import * as styles from '../../constants/styles';
@@ -33,7 +33,7 @@ export class TargetDatabaseSummaryDialog {
tab.registerContent(async (view: azdata.ModelView) => {
this._view = view;
const isSqlDbMigration = this._model._targetType === MigrationTargetType.SQLDB;
const isSqlDbMigration = this._model.isSqlDbTarget;
const databaseCount = this._view.modelBuilder.text()
.withProps({
value: constants.COUNT_DATABASES(this._model._databasesForMigration.length),

View File

@@ -0,0 +1,664 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as constants from '../../constants/strings';
import { validateIrDatabaseMigrationSettings, validateIrSqlDatabaseMigrationSettings } from '../../api/azure';
import { MigrationStateModel, MigrationTargetType, NetworkShare, ValidateIrState, ValidationResult } from '../../models/stateMachine';
import { EOL } from 'os';
import { IconPathHelper } from '../../constants/iconPathHelper';
const DialogName = 'ValidateIrDialog';
enum ValidationResultIndex {
message = 0,
icon = 1,
status = 2,
errors = 3,
state = 4,
}
export const ValidationStatusLookup: constants.LookupTable<string | undefined> = {
[ValidateIrState.Canceled]: constants.VALIDATION_STATE_CANCELED,
[ValidateIrState.Failed]: constants.VALIDATION_STATE_FAILED,
[ValidateIrState.Pending]: constants.VALIDATION_STATE_PENDING,
[ValidateIrState.Running]: constants.VALIDATION_STATE_RUNNING,
[ValidateIrState.Succeeded]: constants.VALIDATION_STATE_SUCCEEDED,
default: undefined
};
export class ValidateIrDialog {
private _canceled: boolean = true;
private _dialog: azdata.window.Dialog | undefined;
private _disposables: vscode.Disposable[] = [];
private _isOpen: boolean = false;
private _model!: MigrationStateModel;
private _resultsTable!: azdata.TableComponent;
private _startLoader!: azdata.LoadingComponent;
private _startButton!: azdata.ButtonComponent;
private _cancelButton!: azdata.ButtonComponent;
private _copyButton!: azdata.ButtonComponent;
private _validationResult: any[][] = [];
private _valdiationErrors: string[] = [];
private _onClosed: () => void;
constructor(
model: MigrationStateModel,
onClosed: () => void) {
this._model = model;
this._onClosed = onClosed;
}
public async openDialog(dialogTitle: string, results?: ValidationResult[]): Promise<void> {
if (!this._isOpen) {
this._isOpen = true;
this._dialog = azdata.window.createModelViewDialog(
dialogTitle,
DialogName,
600);
const promise = this._initializeDialog(this._dialog);
azdata.window.openDialog(this._dialog);
await promise;
return this._runValidation(results);
}
}
private async _initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
return new Promise<void>((resolve, reject) => {
dialog.registerContent(async (view) => {
try {
dialog.okButton.label = constants.VALIDATE_IR_DONE_BUTTON;
dialog.okButton.position = 'left';
dialog.okButton.enabled = false;
dialog.cancelButton.position = 'left';
this._disposables.push(
dialog.cancelButton.onClick(
e => {
this._canceled = true;
this._saveResults();
this._onClosed();
}));
this._disposables.push(
dialog.okButton.onClick(
e => this._onClosed()));
const headingText = view.modelBuilder.text()
.withProps({
value: constants.VALIDATE_IR_HEADING,
CSSStyles: {
'font-size': '13px',
'font-weight': '400',
'margin-bottom': '10px',
},
})
.component();
this._startLoader = view.modelBuilder.loadingComponent()
.withProps({
loading: false,
CSSStyles: { 'margin': '5px 0 0 10px' }
})
.component();
const headingContainer = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
justifyContent: 'flex-start',
})
.withItems([headingText, this._startLoader], { flex: '0 0 auto' })
.component();
this._resultsTable = await this._createResultsTable(view);
this._startButton = view.modelBuilder.button()
.withProps({
iconPath: IconPathHelper.restartDataCollection,
iconHeight: 18,
iconWidth: 18,
width: 100,
label: constants.VALIDATE_IR_START_VALIDATION,
}).component();
this._cancelButton = view.modelBuilder.button()
.withProps({
iconPath: IconPathHelper.stop,
iconHeight: 18,
iconWidth: 18,
width: 100,
label: constants.VALIDATE_IR_STOP_VALIDATION,
enabled: false,
}).component();
this._copyButton = view.modelBuilder.button()
.withProps({
iconPath: IconPathHelper.copy,
iconHeight: 18,
iconWidth: 18,
width: 150,
label: constants.VALIDATE_IR_COPY_RESULTS,
enabled: false,
}).component();
this._disposables.push(
this._startButton.onDidClick(
async (e) => await this._runValidation()));
this._disposables.push(
this._cancelButton.onDidClick(
e => {
this._cancelButton.enabled = false;
this._canceled = true;
}));
this._disposables.push(
this._copyButton.onDidClick(
async (e) => this._copyValidationResults()));
const toolbar = view.modelBuilder.toolbarContainer()
.withToolbarItems([
{ component: this._startButton },
{ component: this._cancelButton },
{ component: this._copyButton }])
.component();
const resultsHeading = view.modelBuilder.text()
.withProps({
value: constants.VALIDATE_IR_RESULTS_HEADING,
CSSStyles: {
'font-size': '16px',
'font-weight': '600',
'margin-bottom': '10px'
},
})
.component();
const resultsText = view.modelBuilder.inputBox()
.withProps({
inputType: 'text',
height: 200,
multiline: true,
CSSStyles: { 'overflow': 'none auto' }
})
.component();
this._disposables.push(
this._resultsTable.onRowSelected(
async (e) => await this._updateResultsInfoBox(resultsText)));
const flex = view.modelBuilder.flexContainer()
.withItems([
headingContainer,
toolbar,
this._resultsTable,
resultsHeading,
resultsText],
{ flex: '0 0 auto' })
.withProps({ CSSStyles: { 'margin': '0 0 0 15px' } })
.withLayout({
flexFlow: 'column',
height: '100%',
width: 565,
}).component();
this._disposables.push(
view.onClosed(e =>
this._disposables.forEach(
d => { try { d.dispose(); } catch { } })));
await view.initializeModel(flex);
resolve();
} catch (ex) {
reject(ex);
}
});
});
}
private _saveResults(): void {
const results = this._validationResults();
switch (this._model._targetType) {
case MigrationTargetType.SQLDB:
this._model._validateIrSqlDb = results;
break;
case MigrationTargetType.SQLMI:
this._model._validateIrSqlMi = results;
break;
case MigrationTargetType.SQLVM:
this._model._validateIrSqlVm = results;
break;
}
}
private _validationResults(): ValidationResult[] {
return this._validationResult.map(result => {
const state = result[ValidationResultIndex.state];
const finalState = this._canceled
? state === ValidateIrState.Running || state === ValidateIrState.Pending
? ValidateIrState.Canceled
: state
: state;
const errors = result[ValidationResultIndex.errors] ?? [];
return {
errors: errors,
state: finalState,
};
});
}
private async _runValidation(results?: ValidationResult[]): Promise<void> {
try {
this._startLoader.loading = true;
this._startButton.enabled = false;
this._cancelButton.enabled = true;
this._copyButton.enabled = false;
this._dialog!.okButton.enabled = false;
this._dialog!.cancelButton.enabled = true;
if (this._model.isIrTargetValidated && results) {
await this._initializeResults(results);
} else {
await this._validate();
}
} finally {
this._startLoader.loading = false;
this._startButton.enabled = true;
this._cancelButton.enabled = false;
this._copyButton.enabled = true;
this._dialog!.okButton.enabled = this._model.isIrTargetValidated;
this._dialog!.cancelButton.enabled = !this._model.isIrTargetValidated;
}
}
private async _copyValidationResults(): Promise<void> {
const errorsText = this._valdiationErrors.join(EOL);
const msg = errorsText.length === 0
? constants.VALIDATE_IR_VALIDATION_COMPLETED
: constants.VALIDATE_IR_VALIDATION_COMPLETED_ERRORS(errorsText);
return vscode.env.clipboard.writeText(msg);
}
private async _updateResultsInfoBox(text: azdata.InputBoxComponent): Promise<void> {
const selectedRows: number[] = this._resultsTable.selectedRows ?? [];
const statusMessages: string[] = [];
if (selectedRows.length > 0) {
for (let i = 0; i < selectedRows.length; i++) {
const row = selectedRows[i];
const results: any[] = this._validationResult[row];
const status = results[ValidationResultIndex.status];
const errors = results[ValidationResultIndex.errors];
statusMessages.push(
constants.VALIDATE_IR_VALIDATION_STATUS(ValidationStatusLookup[status], errors));
}
}
const msg = statusMessages.length > 0
? statusMessages.join(EOL)
: '';
text.value = msg;
}
private async _createResultsTable(view: azdata.ModelView): Promise<azdata.TableComponent> {
return view.modelBuilder.table()
.withProps({
columns: [
{
value: 'test',
name: constants.VALIDATE_IR_COLUMN_VALIDATION_STEPS,
type: azdata.ColumnType.text,
width: 380,
headerCssClass: 'no-borders',
cssClass: 'no-borders align-with-header',
},
{
value: 'image',
name: '',
type: azdata.ColumnType.icon,
width: 20,
headerCssClass: 'no-borders display-none',
cssClass: 'no-borders align-with-header',
},
{
value: 'message',
name: constants.VALIDATE_IR_COLUMN_STATUS,
type: azdata.ColumnType.text,
width: 150,
headerCssClass: 'no-borders',
cssClass: 'no-borders align-with-header',
},
],
data: [],
width: 580,
height: 300,
CSSStyles: {
'margin-top': '10px',
'margin-bottom': '10px',
},
})
.component();
}
private async _initializeResults(results?: ValidationResult[]): Promise<void> {
this._valdiationErrors = [];
if (this._model.isSqlDbTarget) {
// initialize validation results view for sqldb target
await this._initSqlDbIrResults(results);
} else {
// initialize validation results view for sqlmi, sqlvm targets
await this._initTestIrResults(results);
}
}
private async _validate(): Promise<void> {
this._canceled = false;
await this._initializeResults();
if (this._model.isSqlDbTarget) {
await this._validateSqlDbMigration();
} else {
await this._validateDatabaseMigration();
}
this._saveResults();
}
private async _validateDatabaseMigration(): Promise<void> {
const sqlConnections = await azdata.connection.getConnections();
const currentConnection = sqlConnections.find(
value => value.connectionId === this._model.sourceConnectionId);
const sourceServerName = currentConnection?.serverName!;
const trustServerCertificate = currentConnection?.options?.trustServerCertificate === true;
const databaseCount = this._model._databasesForMigration.length;
const sourceDatabaseName = this._model._databasesForMigration[0];
const networkShare = this._model._databaseBackup.networkShares[0];
let testNumber: number = 0;
const validate = async (
sourceDatabase: string,
network: NetworkShare,
testIrOnline: boolean,
testSourceLocationConnectivity: boolean,
testSourceConnectivity: boolean,
testBlobConnectivity: boolean): Promise<boolean> => {
try {
await this._updateValidateIrResults(testNumber, ValidateIrState.Running);
const response = await validateIrDatabaseMigrationSettings(
this._model,
sourceServerName,
trustServerCertificate,
sourceDatabase,
network,
testIrOnline,
testSourceLocationConnectivity,
testSourceConnectivity,
testBlobConnectivity);
if (response?.errors?.length > 0) {
const errors = response.errors.map(
error => constants.VALIDATE_IR_VALIDATION_RESULT_ERROR(
sourceDatabase,
network.networkShareLocation,
error));
await this._updateValidateIrResults(testNumber, ValidateIrState.Failed, errors);
} else {
await this._updateValidateIrResults(testNumber, ValidateIrState.Succeeded);
return true;
}
} catch (error) {
await this._updateValidateIrResults(
testNumber,
ValidateIrState.Failed,
[constants.VALIDATE_IR_VALIDATION_RESULT_API_ERROR(sourceDatabase, error)]);
}
return false;
};
// validate integration runtime (IR) is online
if (!await validate(sourceDatabaseName, networkShare, true, false, false, false)) {
this._canceled = true;
await this._updateValidateIrResults(testNumber + 1, ValidateIrState.Canceled, [constants.VALIDATE_IR_VALIDATION_CANCELED])
return;
}
testNumber++;
// validate blob container connectivity
if (!await validate(sourceDatabaseName, networkShare, false, false, false, true)) {
await this._updateValidateIrResults(testNumber + 1, ValidateIrState.Canceled, [constants.VALIDATE_IR_VALIDATION_CANCELED])
return;
}
for (let i = 0; i < databaseCount; i++) {
const sourceDatabaseName = this._model._databasesForMigration[i];
const networkShare = this._model._databaseBackup.networkShares[i];
testNumber++;
if (this._canceled) {
await this._updateValidateIrResults(testNumber, ValidateIrState.Canceled, [constants.VALIDATE_IR_VALIDATION_CANCELED])
break;
}
// validate source connectivity
await validate(sourceDatabaseName, networkShare, false, false, true, false);
testNumber++;
if (this._canceled) {
await this._updateValidateIrResults(testNumber, ValidateIrState.Canceled, [constants.VALIDATE_IR_VALIDATION_CANCELED])
break;
}
// valdiate source location / network share connectivity
await validate(sourceDatabaseName, networkShare, false, true, false, false);
}
}
private async _validateSqlDbMigration(): Promise<void> {
const sqlConnections = await azdata.connection.getConnections();
const currentConnection = sqlConnections.find(
value => value.connectionId === this._model.sourceConnectionId);
const sourceServerName = currentConnection?.serverName!;
const trustServerCertificate = currentConnection?.options['trustServerCertificate'] === true;
const databaseCount = this._model._databasesForMigration.length;
const sourceDatabaseName = this._model._databasesForMigration[0];
const targetDatabaseName = this._model._sourceTargetMapping.get(sourceDatabaseName)?.databaseName ?? '';
let testNumber: number = 0;
const validate = async (
sourceDatabase: string,
targetDatabase: string,
testIrOnline: boolean,
testSourceConnectivity: boolean,
testTargetConnectivity: boolean): Promise<boolean> => {
await this._updateValidateIrResults(testNumber, ValidateIrState.Running);
try {
const response = await validateIrSqlDatabaseMigrationSettings(
this._model,
sourceServerName,
trustServerCertificate,
sourceDatabase,
targetDatabase,
testIrOnline,
testSourceConnectivity,
testTargetConnectivity);
if (response?.errors?.length > 0) {
const errors = response.errors.map(
error => constants.VALIDATE_IR_SQLDB_VALIDATION_RESULT_ERROR(
sourceDatabase,
targetDatabase,
error));
await this._updateValidateIrResults(testNumber, ValidateIrState.Failed, errors);
} else {
await this._updateValidateIrResults(testNumber, ValidateIrState.Succeeded);
return true;
}
} catch (error) {
await this._updateValidateIrResults(
testNumber,
ValidateIrState.Failed,
[constants.VALIDATE_IR_VALIDATION_RESULT_API_ERROR(sourceDatabase, error)]);
}
return false;
};
// validate IR is online
if (!await validate(sourceDatabaseName, targetDatabaseName, true, false, false)) {
this._canceled = true;
await this._updateValidateIrResults(testNumber + 1, ValidateIrState.Canceled, [constants.VALIDATE_IR_VALIDATION_CANCELED]);
return;
}
for (let i = 0; i < databaseCount; i++) {
const sourceDatabaseName = this._model._databasesForMigration[i];
const targetDatabaseName = this._model._sourceTargetMapping.get(sourceDatabaseName)?.databaseName ?? '';
testNumber++;
if (this._canceled) {
await this._updateValidateIrResults(testNumber, ValidateIrState.Canceled, [constants.VALIDATE_IR_VALIDATION_CANCELED]);
break;
}
// validate source connectivity
await validate(sourceDatabaseName, targetDatabaseName, false, true, false);
testNumber++;
if (this._canceled) {
await this._updateValidateIrResults(testNumber, ValidateIrState.Canceled, [constants.VALIDATE_IR_VALIDATION_CANCELED]);
break;
}
// validate target connectivity
await validate(sourceDatabaseName, targetDatabaseName, false, false, true);
}
}
private async _initTestIrResults(results?: ValidationResult[]): Promise<void> {
this._validationResult = [];
this._addValidationResult(constants.VALIDATE_IR_VALIDATION_RESULT_LABEL_SHIR);
this._addValidationResult(constants.VALIDATE_IR_VALIDATION_RESULT_LABEL_STORAGE);
for (let i = 0; i < this._model._databasesForMigration.length; i++) {
const sourceDatabaseName = this._model._databasesForMigration[i];
const networkShare = this._model._databaseBackup.networkShares[i];
this._addValidationResult(
constants.VALIDATE_IR_VALIDATION_RESULT_LABEL_SOURCE_DATABASE(
sourceDatabaseName));
this._addValidationResult(
constants.VALIDATE_IR_VALIDATION_RESULT_LABEL_NETWORK_SHARE(
networkShare.networkShareLocation));
}
if (results && results.length > 0) {
for (let row = 0; row < results.length; row++) {
await this._updateValidateIrResults(
row,
results[row].state,
results[row].errors,
false);
}
}
const data = this._validationResult.map(row => [
row[ValidationResultIndex.message],
row[ValidationResultIndex.icon],
row[ValidationResultIndex.status]]);
await this._resultsTable.updateProperty('data', data);
}
private async _initSqlDbIrResults(results?: ValidationResult[]): Promise<void> {
this._validationResult = [];
this._addValidationResult(constants.VALIDATE_IR_VALIDATION_RESULT_LABEL_SHIR);
this._model._databasesForMigration
.forEach(sourceDatabaseName => {
this._addValidationResult(
constants.VALIDATE_IR_VALIDATION_RESULT_LABEL_SOURCE_DATABASE(
sourceDatabaseName));
const targetDatabaseName = this._model._sourceTargetMapping.get(sourceDatabaseName)?.databaseName ?? '';
this._addValidationResult(
constants.VALIDATE_IR_VALIDATION_RESULT_LABEL_TARGET_DATABASE(
targetDatabaseName));
});
if (results && results.length > 0) {
for (let row = 0; row < results.length; row++) {
await this._updateValidateIrResults(
row,
results[row].state,
results[row].errors,
false);
}
}
const data = this._validationResult.map(row => [
row[ValidationResultIndex.message],
row[ValidationResultIndex.icon],
row[ValidationResultIndex.status]]);
await this._resultsTable.updateProperty('data', data);
}
private _addValidationResult(message: string): void {
this._validationResult.push([
message,
<azdata.IconColumnCellValue>{
icon: IconPathHelper.notStartedMigration,
title: ValidationStatusLookup[ValidateIrState.Pending],
},
ValidationStatusLookup[ValidateIrState.Pending],
[],
ValidateIrState.Pending]);
}
private async _updateValidateIrResults(row: number, state: ValidateIrState, errors: string[] = [], updateTable: boolean = true): Promise<void> {
if (state === ValidateIrState.Canceled) {
for (let cancelRow = row; cancelRow < this._validationResult.length; cancelRow++) {
await this._updateResults(cancelRow, state, errors);
}
} else {
await this._updateResults(row, state, errors);
}
if (updateTable) {
const data = this._validationResult.map(row => [
row[ValidationResultIndex.message],
row[ValidationResultIndex.icon],
row[ValidationResultIndex.status]]);
await this._resultsTable.updateProperty('data', data);
}
this._valdiationErrors.push(...errors);
}
private async _updateResults(row: number, state: ValidateIrState, errors: string[] = []): Promise<void> {
const result = this._validationResult[row];
const status = ValidationStatusLookup[state];
const statusMsg = state === ValidateIrState.Failed && errors.length > 0
? constants.VALIDATE_IR_VALIDATION_STATUS_ERROR_COUNT(status, errors.length)
: status;
const statusMessage = errors.length > 0
? constants.VALIDATE_IR_VALIDATION_STATUS_ERROR(status, errors)
: statusMsg;
this._validationResult[row] = [
result[ValidationResultIndex.message],
<azdata.IconColumnCellValue>{
icon: this._getValidationStateImage(state),
title: statusMessage,
},
statusMsg,
errors,
state];
}
private _getValidationStateImage(state: ValidateIrState): azdata.IconPath {
switch (state) {
case ValidateIrState.Canceled:
return IconPathHelper.cancel;
case ValidateIrState.Failed:
return IconPathHelper.error;
case ValidateIrState.Running:
return IconPathHelper.inProgressMigration;
case ValidateIrState.Succeeded:
return IconPathHelper.completedMigration;
case ValidateIrState.Pending:
default:
return IconPathHelper.notStartedMigration;
}
}
}