add error banner for failed migration cutover and cancel migration (#17106)

This commit is contained in:
brian-harris
2021-09-21 23:31:36 -07:00
committed by GitHub
parent 4a715e473a
commit 155ea4c707
26 changed files with 250 additions and 224 deletions

View File

@@ -115,9 +115,9 @@ export class ConfirmCutoverDialog {
this._dialogObject.okButton.enabled = false;
this._dialogObject.okButton.label = constants.COMPLETE_CUTOVER;
this._disposables.push(this._dialogObject.okButton.onClick((e) => {
this.migrationCutoverModel.startCutover();
vscode.window.showInformationMessage(constants.CUTOVER_IN_PROGRESS(this.migrationCutoverModel._migration.migrationContext.properties.sourceDatabaseName));
this._disposables.push(this._dialogObject.okButton.onClick(async (e) => {
await this.migrationCutoverModel.startCutover();
void vscode.window.showInformationMessage(constants.CUTOVER_IN_PROGRESS(this.migrationCutoverModel._migration.migrationContext.properties.sourceDatabaseName));
}));
const formBuilder = view.modelBuilder.formContainer().withFormItems(
@@ -235,14 +235,14 @@ export class ConfirmCutoverDialog {
if (expanded) {
containerHeading.iconPath = IconPathHelper.expandButtonClosed;
containerHeading.iconHeight = 12;
fileTable.updateCssStyles({
await fileTable.updateCssStyles({
'display': 'none'
});
} else {
containerHeading.iconPath = IconPathHelper.expandButtonOpen;
containerHeading.iconHeight = 8;
fileTable.updateCssStyles({
await fileTable.updateCssStyles({
'display': 'inline'
});
}

View File

@@ -6,10 +6,10 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { IconPathHelper } from '../../constants/iconPathHelper';
import { MigrationContext, MigrationStatus } from '../../models/migrationLocalStorage';
import { BackupFileInfoStatus, MigrationContext, MigrationStatus } from '../../models/migrationLocalStorage';
import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
import * as loc from '../../constants/strings';
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage, SupportedAutoRefreshIntervals, clearDialogMessage } from '../../api/utils';
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage, SupportedAutoRefreshIntervals, clearDialogMessage, displayDialogErrorMessage } from '../../api/utils';
import { EOL } from 'os';
import { ConfirmCutoverDialog } from './confirmCutoverDialog';
@@ -201,7 +201,7 @@ export class MigrationCutoverDialog {
let formItems = [
{ component: this.migrationContainerHeader() },
{ component: this._view.modelBuilder.separator().withProps({ width: 1000 }).component() },
{ component: this.migrationInfoGrid() },
{ component: await this.migrationInfoGrid() },
{ component: this._view.modelBuilder.separator().withProps({ width: 1000 }).component() },
{ component: this._fileCount },
{ component: this._fileTable },
@@ -316,6 +316,10 @@ export class MigrationCutoverDialog {
const dialog = new ConfirmCutoverDialog(this._model);
await dialog.initialize();
await this.refreshStatus();
if (this._model.CutoverError) {
displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_CUTOVER_ERROR, this._model.CutoverError);
}
}));
headerActions.addItem(this._cutoverButton, {
@@ -336,10 +340,13 @@ export class MigrationCutoverDialog {
}).component();
this._disposables.push(this._cancelButton.onDidClick((e) => {
vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, { modal: true }, loc.YES, loc.NO).then(async (v) => {
void vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, { modal: true }, loc.YES, loc.NO).then(async (v) => {
if (v === loc.YES) {
await this._model.cancelMigration();
await this.refreshStatus();
if (this._model.CancelMigrationError) {
displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_CANCELLATION_ERROR, this._model.CancelMigrationError);
}
}
});
}));
@@ -382,9 +389,9 @@ export class MigrationCutoverDialog {
this._disposables.push(this._copyDatabaseMigrationDetails.onDidClick(async (e) => {
await this.refreshStatus();
vscode.env.clipboard.writeText(this.getMigrationDetails());
await vscode.env.clipboard.writeText(this.getMigrationDetails());
vscode.window.showInformationMessage(loc.DETAILS_COPIED);
void vscode.window.showInformationMessage(loc.DETAILS_COPIED);
}));
headerActions.addItem(this._copyDatabaseMigrationDetails, {
@@ -451,7 +458,7 @@ export class MigrationCutoverDialog {
return header;
}
private migrationInfoGrid(): azdata.FlexContainer {
private async migrationInfoGrid(): Promise<azdata.FlexContainer> {
const addInfoFieldToContainer = (infoField: InfoFieldSchema, container: azdata.FlexContainer): void => {
container.addItem(infoField.flexContainer, {
CSSStyles: {
@@ -463,9 +470,9 @@ export class MigrationCutoverDialog {
const flexServer = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
this._sourceDatabaseInfoField = this.createInfoField(loc.SOURCE_DATABASE, '');
this._sourceDetailsInfoField = this.createInfoField(loc.SOURCE_SERVER, '');
this._sourceVersionInfoField = this.createInfoField(loc.SOURCE_VERSION, '');
this._sourceDatabaseInfoField = await this.createInfoField(loc.SOURCE_DATABASE, '');
this._sourceDetailsInfoField = await this.createInfoField(loc.SOURCE_SERVER, '');
this._sourceVersionInfoField = await this.createInfoField(loc.SOURCE_VERSION, '');
addInfoFieldToContainer(this._sourceDatabaseInfoField, flexServer);
addInfoFieldToContainer(this._sourceDetailsInfoField, flexServer);
addInfoFieldToContainer(this._sourceVersionInfoField, flexServer);
@@ -473,9 +480,9 @@ export class MigrationCutoverDialog {
const flexTarget = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
this._targetDatabaseInfoField = this.createInfoField(loc.TARGET_DATABASE_NAME, '');
this._targetServerInfoField = this.createInfoField(loc.TARGET_SERVER, '');
this._targetVersionInfoField = this.createInfoField(loc.TARGET_VERSION, '');
this._targetDatabaseInfoField = await this.createInfoField(loc.TARGET_DATABASE_NAME, '');
this._targetServerInfoField = await this.createInfoField(loc.TARGET_SERVER, '');
this._targetVersionInfoField = await this.createInfoField(loc.TARGET_VERSION, '');
addInfoFieldToContainer(this._targetDatabaseInfoField, flexTarget);
addInfoFieldToContainer(this._targetServerInfoField, flexTarget);
addInfoFieldToContainer(this._targetVersionInfoField, flexTarget);
@@ -484,9 +491,9 @@ export class MigrationCutoverDialog {
const flexStatus = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
this._migrationStatusInfoField = this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
this._fullBackupFileOnInfoField = this.createInfoField(loc.FULL_BACKUP_FILES, '', isBlobMigration);
this._backupLocationInfoField = this.createInfoField(loc.BACKUP_LOCATION, '');
this._migrationStatusInfoField = await this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
this._fullBackupFileOnInfoField = await this.createInfoField(loc.FULL_BACKUP_FILES, '', isBlobMigration);
this._backupLocationInfoField = await this.createInfoField(loc.BACKUP_LOCATION, '');
addInfoFieldToContainer(this._migrationStatusInfoField, flexStatus);
addInfoFieldToContainer(this._fullBackupFileOnInfoField, flexStatus);
addInfoFieldToContainer(this._backupLocationInfoField, flexStatus);
@@ -494,9 +501,9 @@ export class MigrationCutoverDialog {
const flexFile = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
this._lastLSNInfoField = this.createInfoField(loc.LAST_APPLIED_LSN, '', isBlobMigration);
this._lastAppliedBackupInfoField = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
this._lastAppliedBackupTakenOnInfoField = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '', isBlobMigration);
this._lastLSNInfoField = await this.createInfoField(loc.LAST_APPLIED_LSN, '', isBlobMigration);
this._lastAppliedBackupInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
this._lastAppliedBackupTakenOnInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '', isBlobMigration);
addInfoFieldToContainer(this._lastLSNInfoField, flexFile);
addInfoFieldToContainer(this._lastAppliedBackupInfoField, flexFile);
addInfoFieldToContainer(this._lastAppliedBackupTakenOnInfoField, flexFile);
@@ -558,7 +565,7 @@ export class MigrationCutoverDialog {
clearDialogMessage(this._dialogObject);
if (this._isOnlineMigration()) {
this._cutoverButton.updateCssStyles({
await this._cutoverButton.updateCssStyles({
'display': 'inline'
});
}
@@ -660,22 +667,22 @@ export class MigrationCutoverDialog {
this._lastAppliedBackupTakenOnInfoField.text.value = lastAppliedBackupFileTakenOn! ? convertIsoTimeToLocalTime(lastAppliedBackupFileTakenOn).toLocaleString() : '-';
if (this._shouldDisplayBackupFileTable()) {
this._fileCount.updateCssStyles({
await this._fileCount.updateCssStyles({
display: 'inline'
});
this._fileTable.updateCssStyles({
await this._fileTable.updateCssStyles({
display: 'inline'
});
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
if (tableData.length === 0) {
this._emptyTableFill.updateCssStyles({
await this._emptyTableFill.updateCssStyles({
'display': 'flex'
});
this._fileTable.height = '50px';
} else {
this._emptyTableFill.updateCssStyles({
await this._emptyTableFill.updateCssStyles({
'display': 'none'
});
this._fileTable.height = '300px';
@@ -698,25 +705,21 @@ export class MigrationCutoverDialog {
}
}
this._cutoverButton.enabled = false;
if (migrationStatusTextValue === MigrationStatus.InProgress) {
const restoredCount = (this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.filter(a => a.listOfBackupFiles[0].status === 'Restored'))?.length ?? 0;
const restoredCount = this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.filter(
(a) => a.listOfBackupFiles[0].status === BackupFileInfoStatus.Restored)?.length ?? 0;
if (restoredCount > 0 || isBlobMigration) {
this._cutoverButton.enabled = true;
}
} else {
this._cutoverButton.enabled = false;
}
this._cancelButton.enabled =
migrationStatusTextValue === MigrationStatus.Creating ||
migrationStatusTextValue === MigrationStatus.InProgress;
} catch (e) {
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: loc.MIGRATION_STATUS_REFRESH_ERROR,
description: e.message
};
displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_STATUS_REFRESH_ERROR, e);
console.log(e);
} finally {
this.isRefreshing = false;
@@ -724,17 +727,17 @@ export class MigrationCutoverDialog {
}
}
private createInfoField(label: string, value: string, defaultHidden: boolean = false, iconPath?: azdata.IconPath): {
private async createInfoField(label: string, value: string, defaultHidden: boolean = false, iconPath?: azdata.IconPath): Promise<{
flexContainer: azdata.FlexContainer,
text: azdata.TextComponent,
icon?: azdata.ImageComponent
} {
}> {
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
if (defaultHidden) {
flexContainer.updateCssStyles({
await flexContainer.updateCssStyles({
'display': 'none'
});
}

View File

@@ -4,12 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { getMigrationStatus, DatabaseMigration, startMigrationCutover, stopMigration, getMigrationAsyncOperationDetails, AzureAsyncOperationResource, BackupFileInfo, getResourceGroupFromId } from '../../api/azure';
import { MigrationContext } from '../../models/migrationLocalStorage';
import { BackupFileInfoStatus, MigrationContext } from '../../models/migrationLocalStorage';
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews } from '../../telemtery';
import * as constants from '../../constants/strings';
import { getMigrationTargetType, getMigrationMode } from '../../constants/helper';
export class MigrationCutoverDialogModel {
public CutoverError?: Error;
public CancelMigrationError?: Error;
public migrationStatus!: DatabaseMigration;
public migrationOpStatus!: AzureAsyncOperationResource;
@@ -47,6 +49,7 @@ export class MigrationCutoverDialogModel {
public async startCutover(): Promise<DatabaseMigration | undefined> {
try {
this.CutoverError = undefined;
if (this.migrationStatus) {
const cutover = await startMigrationCutover(
this._migration.azureAccount,
@@ -66,6 +69,7 @@ export class MigrationCutoverDialogModel {
return cutover;
}
} catch (error) {
this.CutoverError = error;
console.log(error);
}
return undefined!;
@@ -73,6 +77,7 @@ export class MigrationCutoverDialogModel {
public async cancelMigration(): Promise<void> {
try {
this.CancelMigrationError = undefined;
if (this.migrationStatus) {
const cutoverStartTime = new Date().toString();
await stopMigration(
@@ -93,6 +98,7 @@ export class MigrationCutoverDialogModel {
);
}
} catch (error) {
this.CancelMigrationError = error;
console.log(error);
}
return undefined!;
@@ -126,7 +132,7 @@ export class MigrationCutoverDialogModel {
const files: BackupFileInfo[] = [];
this.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.forEach(abs => {
abs.listOfBackupFiles.forEach(f => {
if (f.status !== 'Restored') {
if (f.status !== BackupFileInfoStatus.Restored) {
files.push(f);
}
});