Migration extension UX update and bug fixes. (#15384)

* Adding a null check to prevent infinite next button loading on account selection page.

* Remove a useless validation in migration cutover page

* Fixed some component formatting in source selection page

* Completely updated target selection page UX according to latest figma mockup

* Adding confirmation for migration cutover and cancel

* migration vbump

* azdata vbump in migration extension

* letting users do a cutover with unrestored files

* Fixing some localized strings

* Adding readme file for migration extension.

* Adding a static link for readme gif

* added sql mi typing, localized strings, some null checks

* casting target instance as sql mi
This commit is contained in:
Aasim Khan
2021-05-11 16:27:16 +00:00
committed by GitHub
parent fff2bd5089
commit 0b41baaa0c
15 changed files with 495 additions and 242 deletions

View File

@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* 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 { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
import * as constants from '../../constants/strings';
import * as vscode from 'vscode';
import { SqlManagedInstance } from '../../api/azure';
export class ConfirmCutoverDialog {
private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView;
constructor(private migrationCutoverModel: MigrationCutoverDialogModel) {
this._dialogObject = azdata.window.createModelViewDialog('', 'ConfirmCutoverDialog', 500);
}
async initialize(): Promise<void> {
let tab = azdata.window.createTab('');
tab.registerContent(async (view: azdata.ModelView) => {
this._view = view;
const completeCutoverText = view.modelBuilder.text().withProps({
value: constants.COMPLETE_CUTOVER,
CSSStyles: {
'font-size': '20px',
'font-weight': 'bold',
'margin-bottom': '0px'
}
}).component();
const sourceDatabaseText = view.modelBuilder.text().withProps({
value: this.migrationCutoverModel._migration.migrationContext.properties.sourceDatabaseName,
CSSStyles: {
'font-size': '10px',
'margin': '5px 0px 10px 0px'
}
}).component();
const separator = this._view.modelBuilder.separator().withProps({ width: '800px' }).component();
let infoDisplay = 'none';
if (this.migrationCutoverModel._migration.targetManagedInstance.id.toLocaleLowerCase().includes('managedinstances')
&& (<SqlManagedInstance>this.migrationCutoverModel._migration.targetManagedInstance)?.sku?.tier === 'BusinessCritical') {
infoDisplay = 'inline';
}
const businessCriticalinfoBox = this._view.modelBuilder.infoBox().withProps({
text: constants.BUSINESS_CRITICAL_INFO,
style: 'information',
CSSStyles: {
'font-size': '13px',
'display': infoDisplay
}
}).component();
const helpMainText = this._view.modelBuilder.text().withProps({
value: constants.CUTOVER_HELP_MAIN,
CSSStyles: {
'font-size': '13px',
}
}).component();
const helpStepsText = this._view.modelBuilder.text().withProps({
value: `${constants.CUTOVER_HELP_STEP1}
${constants.CUTOVER_HELP_STEP2}
${constants.CUTOVER_HELP_STEP3}`,
CSSStyles: {
'font-size': '13px',
}
}).component();
const pendingBackupCount = this.migrationCutoverModel.migrationStatus.properties.migrationStatusDetails?.activeBackupSets.filter(f => f.listOfBackupFiles[0].status !== 'Restored' && f.listOfBackupFiles[0].status !== 'Ignored').length;
const pendingText = this._view.modelBuilder.text().withProps({
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold'
},
value: constants.PENDING_BACKUPS(pendingBackupCount!)
}).component();
const confirmCheckbox = this._view.modelBuilder.checkBox().withProps({
CSSStyles: {
'font-size': '13px',
},
label: constants.CONFIRM_CUTOVER_CHECKBOX,
}).component();
confirmCheckbox.onChanged(e => {
this._dialogObject.okButton.enabled = e;
});
const container = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).withItems([
completeCutoverText,
sourceDatabaseText,
separator,
businessCriticalinfoBox,
helpMainText,
helpStepsText,
pendingText,
confirmCheckbox
]).component();
this._dialogObject.okButton.enabled = false;
this._dialogObject.okButton.label = constants.COMPLETE_CUTOVER;
this._dialogObject.okButton.onClick((e) => {
this.migrationCutoverModel.startCutover();
vscode.window.showInformationMessage(constants.CUTOVER_IN_PROGRESS(this.migrationCutoverModel._migration.migrationContext.properties.sourceDatabaseName));
});
const formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: container
}
],
{
horizontal: false
}
);
const form = formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(form);
});
this._dialogObject.content = [tab];
azdata.window.openDialog(this._dialogObject);
}
}

View File

@@ -11,6 +11,7 @@ import * as loc from '../../constants/strings';
import { getSqlServerName } from '../../api/utils';
import { EOL } from 'os';
import * as vscode from 'vscode';
import { ConfirmCutoverDialog } from './confirmCutoverDialog';
export class MigrationCutoverDialog {
private _dialogObject!: azdata.window.Dialog;
@@ -41,8 +42,6 @@ export class MigrationCutoverDialog {
private fileTable!: azdata.TableComponent;
private _startCutover!: boolean;
constructor(migration: MigrationContext) {
this._model = new MigrationCutoverDialogModel(migration);
this._dialogObject = azdata.window.createModelViewDialog('', 'MigrationCutoverDialog', 1000);
@@ -333,22 +332,17 @@ export class MigrationCutoverDialog {
iconPath: IconPathHelper.cutover,
iconHeight: '14px',
iconWidth: '12px',
label: 'Start Cutover',
label: loc.COMPLETE_CUTOVER,
height: '20px',
width: '100px',
width: '130px',
enabled: false
}).component();
this._cutoverButton.onDidClick(async (e) => {
if (this._startCutover) {
await this._model.startCutover();
this.refreshStatus();
} else {
this._dialogObject.message = {
text: loc.CANNOT_START_CUTOVER_ERROR,
level: azdata.window.MessageLevel.Error
};
}
await this.refreshStatus();
const dialog = new ConfirmCutoverDialog(this._model);
await dialog.initialize();
await this.refreshStatus();
});
headerActions.addItem(this._cutoverButton, {
@@ -365,7 +359,12 @@ export class MigrationCutoverDialog {
}).component();
this._cancelButton.onDidClick((e) => {
this.cancelMigration();
vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, loc.YES, loc.NO).then(async (v) => {
if (v === loc.YES) {
await this.cancelMigration();
await this.refreshStatus();
}
});
});
headerActions.addItem(this._cancelButton, {
@@ -537,14 +536,9 @@ export class MigrationCutoverDialog {
row.lastLSN
];
});
if (this._model.migrationStatus.properties.migrationStatusDetails?.isFullBackupRestored) {
this._startCutover = true;
}
if (migrationStatusTextValue === MigrationStatus.InProgress) {
const fileNotRestored = await tableData.some(file => file.status !== 'Restored' && file.status !== 'Ignored');
this._cutoverButton.enabled = !fileNotRestored;
this._cancelButton.enabled = true;
this._cutoverButton.enabled = tableData.length > 0;
} else {
this._cutoverButton.enabled = false;
this._cancelButton.enabled = false;