Files
azuredatastudio/extensions/sql-migration/src/dialog/migrationCutover/confirmCutoverDialog.ts
brian-harris 3a0ac7279a Dev/brih/feature/switch ads to portal context (#18963)
* Add CodeQL Analysis workflow (#10195)

* Add CodeQL Analysis workflow

* Fix path

* dashboard refactor

* update version, readme, minor ui changes

* fix merge issue

* Revert "Add CodeQL Analysis workflow (#10195)"

This reverts commit fe98d586cd75be4758ac544649bb4983accf4acd.

* fix context switching issue

* fix resource id parsing error and mi api version

* mv refresh btn, rm autorefresh, align cards

* remove missed autorefresh code

* improve error handling and messages

* fix typos

* remove duplicate/unnecessary  _populate* calls

* change clear configuration button text

* remove confusing watermark text

* add stale account handling

Co-authored-by: Justin Hutchings <jhutchings1@users.noreply.github.com>
2022-04-12 16:26:40 -07:00

313 lines
9.8 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* 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 { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
import * as constants from '../../constants/strings';
import { getMigrationTargetInstance, SqlManagedInstance } from '../../api/azure';
import { IconPathHelper } from '../../constants/iconPathHelper';
import { convertByteSizeToReadableUnit, get12HourTime } from '../../api/utils';
import * as styles from '../../constants/styles';
import { isBlobMigration } from '../../constants/helper';
export class ConfirmCutoverDialog {
private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView;
private _disposables: vscode.Disposable[] = [];
constructor(private migrationCutoverModel: MigrationCutoverDialogModel) {
this._dialogObject = azdata.window.createModelViewDialog('', 'ConfirmCutoverDialog', 500);
}
async initialize(): Promise<void> {
const tab = azdata.window.createTab('');
tab.registerContent(async (view: azdata.ModelView) => {
this._view = view;
const completeCutoverText = view.modelBuilder.text().withProps({
value: constants.COMPLETE_CUTOVER,
CSSStyles: { ...styles.PAGE_TITLE_CSS }
}).component();
const sourceDatabaseText = view.modelBuilder.text().withProps({
value: this.migrationCutoverModel._migration.properties.sourceDatabaseName,
CSSStyles: {
...styles.SMALL_NOTE_CSS,
'margin': '4px 0px 8px'
}
}).component();
const separator = this._view.modelBuilder.separator().withProps({ width: '800px' }).component();
const helpMainText = this._view.modelBuilder.text().withProps({
value: constants.CUTOVER_HELP_MAIN,
CSSStyles: { ...styles.BODY_CSS }
}).component();
const helpStepsText = this._view.modelBuilder.text().withProps({
value: this.migrationCutoverModel.confirmCutoverStepsString(),
CSSStyles: {
...styles.BODY_CSS,
'padding': '8px'
}
}).component();
const fileContainer = isBlobMigration(this.migrationCutoverModel.migrationStatus)
? this.createBlobFileContainer()
: this.createNetworkShareFileContainer();
const confirmCheckbox = this._view.modelBuilder.checkBox().withProps({
CSSStyles: {
...styles.BODY_CSS,
'margin-bottom': '12px'
},
label: constants.CONFIRM_CUTOVER_CHECKBOX,
}).component();
this._disposables.push(confirmCheckbox.onChanged(e => {
this._dialogObject.okButton.enabled = e;
}));
const cutoverWarning = this._view.modelBuilder.infoBox().withProps({
text: constants.COMPLETING_CUTOVER_WARNING,
style: 'warning',
CSSStyles: { ...styles.BODY_CSS }
}).component();
let infoDisplay = 'none';
if (this.migrationCutoverModel._migration.id.toLocaleLowerCase().includes('managedinstances')) {
const targetInstance = await getMigrationTargetInstance(
this.migrationCutoverModel._serviceConstext.azureAccount!,
this.migrationCutoverModel._serviceConstext.subscription!,
this.migrationCutoverModel._migration);
if ((<SqlManagedInstance>targetInstance)?.sku?.tier === 'BusinessCritical') {
infoDisplay = 'inline';
}
}
const businessCriticalInfoBox = this._view.modelBuilder.infoBox().withProps({
text: constants.BUSINESS_CRITICAL_INFO,
style: 'information',
CSSStyles: {
...styles.BODY_CSS,
'display': infoDisplay
}
}).component();
const container = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).withItems([
completeCutoverText,
sourceDatabaseText,
separator,
helpMainText,
helpStepsText,
fileContainer,
confirmCheckbox,
cutoverWarning,
businessCriticalInfoBox
]).component();
this._dialogObject.okButton.enabled = false;
this._dialogObject.okButton.label = constants.COMPLETE_CUTOVER;
this._disposables.push(this._dialogObject.okButton.onClick(async (e) => {
await this.migrationCutoverModel.startCutover();
void vscode.window.showInformationMessage(
constants.CUTOVER_IN_PROGRESS(
this.migrationCutoverModel._migration.properties.sourceDatabaseName));
}));
const formBuilder = view.modelBuilder.formContainer().withFormItems(
[{ component: container }],
{ horizontal: false }
);
const form = formBuilder.withLayout({ width: '100%' }).component();
this._disposables.push(this._view.onClosed(e => {
this._disposables.forEach(
d => { try { d.dispose(); } catch { } });
}));
return view.initializeModel(form);
});
this._dialogObject.content = [tab];
azdata.window.openDialog(this._dialogObject);
}
private createBlobFileContainer(): azdata.FlexContainer {
const container = this._view.modelBuilder.flexContainer().withProps({
CSSStyles: { 'margin': '8px 0' }
}).component();
const containerHeading = this._view.modelBuilder.text().withProps({
value: constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0),
width: 250,
CSSStyles: { ...styles.LABEL_CSS }
}).component();
container.addItem(containerHeading, { flex: '0' });
const refreshButton = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.refresh,
iconHeight: 16,
iconWidth: 16,
width: 70,
height: 20,
label: constants.REFRESH,
}).component();
this._disposables.push(refreshButton.onDidClick(async e => {
refreshLoader.loading = true;
try {
await this.migrationCutoverModel.fetchStatus();
containerHeading.value = constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0);
} catch (e) {
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: e.toString()
};
} finally {
refreshLoader.loading = false;
}
}));
container.addItem(refreshButton, { flex: '0' });
const refreshLoader = this._view.modelBuilder.loadingComponent().withProps({
loading: false,
CSSStyles: {
'margin-top': '-4px',
'margin-left': '8px'
}
}).component();
container.addItem(refreshLoader, { flex: '0' });
return container;
}
private createNetworkShareFileContainer(): azdata.FlexContainer {
const container = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();
const headingRow = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'row'
}).component();
let expanded: boolean = false;
const containerHeading = this._view.modelBuilder.button().withProps({
label: constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0),
width: 220,
height: 14,
iconHeight: 12,
iconWidth: 8,
iconPath: IconPathHelper.expandButtonClosed,
CSSStyles: {
...styles.LABEL_CSS,
'margin': '16px 8px 0px 0px'
}
}).component();
this._disposables.push(containerHeading.onDidClick(async e => {
if (expanded) {
containerHeading.iconPath = IconPathHelper.expandButtonClosed;
containerHeading.iconHeight = 12;
await fileTable.updateCssStyles({ 'display': 'none' });
} else {
containerHeading.iconPath = IconPathHelper.expandButtonOpen;
containerHeading.iconHeight = 8;
await fileTable.updateCssStyles({ 'display': 'inline' });
}
expanded = !expanded;
}));
const refreshButton = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.refresh,
iconHeight: 16,
iconWidth: 16,
width: 70,
height: 20,
label: constants.REFRESH,
CSSStyles: { 'margin-top': '13px' }
}).component();
headingRow.addItem(containerHeading, { flex: '0' });
this._disposables.push(refreshButton.onDidClick(async e => {
refreshLoader.loading = true;
try {
await this.migrationCutoverModel.fetchStatus();
containerHeading.label = constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0);
lastScanCompleted.value = constants.LAST_SCAN_COMPLETED(get12HourTime(new Date()));
this.refreshFileTable(fileTable);
} catch (e) {
this._dialogObject.message = {
level: azdata.window.MessageLevel.Error,
text: e.toString()
};
} finally {
refreshLoader.loading = false;
}
}));
headingRow.addItem(refreshButton, { flex: '0' });
const refreshLoader = this._view.modelBuilder.loadingComponent().withProps({
loading: false,
CSSStyles: {
'margin-top': '15px',
'margin-left': '5px',
'height': '13px'
}
}).component();
headingRow.addItem(refreshLoader, { flex: '0' });
container.addItem(headingRow);
const lastScanCompleted = this._view.modelBuilder.text().withProps({
value: constants.LAST_SCAN_COMPLETED(get12HourTime(new Date())),
CSSStyles: { ...styles.NOTE_CSS }
}).component();
container.addItem(lastScanCompleted);
const fileTable = this._view.modelBuilder.table().withProps({
columns: [
{
value: constants.FILE_NAME,
type: azdata.ColumnType.text,
width: 250
},
{
value: constants.STATUS,
type: azdata.ColumnType.text,
width: 80
},
{
value: constants.SIZE_COLUMN_HEADER,
type: azdata.ColumnType.text,
width: 70
}
],
data: [],
width: 400,
height: 150,
CSSStyles: { 'display': 'none' }
}).component();
container.addItem(fileTable);
this.refreshFileTable(fileTable);
return container;
}
private refreshFileTable(fileTable: azdata.TableComponent) {
const pendingFiles = this.migrationCutoverModel.getPendingFiles();
if (pendingFiles.length > 0) {
fileTable.data = pendingFiles.map(f => {
return [
f.fileName,
f.status,
convertByteSizeToReadableUnit(f.totalSize)
];
});
} else {
fileTable.data = [[constants.NO_PENDING_BACKUPS]];
}
}
}