mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-21 17:22:55 -05:00
636 lines
20 KiB
TypeScript
636 lines
20 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 { IconPathHelper } from '../../constants/iconPathHelper';
|
|
import { MigrationContext, MigrationLocalStorage, MigrationStatus } from '../../models/migrationLocalStorage';
|
|
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
|
|
import { AdsMigrationStatus, MigrationStatusDialogModel } from './migrationStatusDialogModel';
|
|
import * as loc from '../../constants/strings';
|
|
import { clearDialogMessage, convertTimeDifferenceToDuration, filterMigrations, getMigrationStatusImage, SupportedAutoRefreshIntervals } from '../../api/utils';
|
|
import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
|
import { ConfirmCutoverDialog } from '../migrationCutover/confirmCutoverDialog';
|
|
import { MigrationCutoverDialogModel } from '../migrationCutover/migrationCutoverDialogModel';
|
|
import { getMigrationTargetType, getMigrationMode } from '../../constants/helper';
|
|
|
|
const refreshFrequency: SupportedAutoRefreshIntervals = 180000;
|
|
|
|
const statusImageSize: number = 14;
|
|
const imageCellStyles: azdata.CssStyles = { 'margin': '3px 3px 0 0', 'padding': '0' };
|
|
const statusCellStyles: azdata.CssStyles = { 'margin': '0', 'padding': '0' };
|
|
|
|
const MenuCommands = {
|
|
Cutover: 'sqlmigration.cutover',
|
|
ViewDatabase: 'sqlmigration.view.database',
|
|
ViewTarget: 'sqlmigration.view.target',
|
|
ViewService: 'sqlmigration.view.service',
|
|
CopyMigration: 'sqlmigration.copy.migration',
|
|
CancelMigration: 'sqlmigration.cancel.migration',
|
|
};
|
|
|
|
export class MigrationStatusDialog {
|
|
private _model: MigrationStatusDialogModel;
|
|
private _dialogObject!: azdata.window.Dialog;
|
|
private _view!: azdata.ModelView;
|
|
private _searchBox!: azdata.InputBoxComponent;
|
|
private _refresh!: azdata.ButtonComponent;
|
|
private _statusDropdown!: azdata.DropDownComponent;
|
|
private _statusTable!: azdata.DeclarativeTableComponent;
|
|
private _refreshLoader!: azdata.LoadingComponent;
|
|
private _autoRefreshHandle!: NodeJS.Timeout;
|
|
private _disposables: vscode.Disposable[] = [];
|
|
|
|
private isRefreshing = false;
|
|
|
|
constructor(migrations: MigrationContext[], private _filter: AdsMigrationStatus) {
|
|
this._model = new MigrationStatusDialogModel(migrations);
|
|
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide');
|
|
}
|
|
|
|
initialize() {
|
|
let tab = azdata.window.createTab('');
|
|
tab.registerContent(async (view: azdata.ModelView) => {
|
|
this._view = view;
|
|
this.registerCommands();
|
|
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
|
[
|
|
{
|
|
component: this.createSearchAndRefreshContainer()
|
|
},
|
|
{
|
|
component: this.createStatusTable()
|
|
}
|
|
],
|
|
{
|
|
horizontal: false
|
|
}
|
|
);
|
|
const form = formBuilder.withLayout({ width: '100%' }).component();
|
|
this._disposables.push(this._view.onClosed(e => {
|
|
clearInterval(this._autoRefreshHandle);
|
|
this._disposables.forEach(
|
|
d => { try { d.dispose(); } catch { } });
|
|
}));
|
|
|
|
return view.initializeModel(form);
|
|
});
|
|
this._dialogObject.content = [tab];
|
|
this._dialogObject.cancelButton.hidden = true;
|
|
this._dialogObject.okButton.label = loc.CLOSE;
|
|
this._disposables.push(this._dialogObject.okButton.onClick(e => {
|
|
clearInterval(this._autoRefreshHandle);
|
|
}));
|
|
azdata.window.openDialog(this._dialogObject);
|
|
}
|
|
|
|
private canCancelMigration = (status: string | undefined) => status &&
|
|
(
|
|
status === MigrationStatus.InProgress ||
|
|
status === MigrationStatus.Creating ||
|
|
status === MigrationStatus.Completing ||
|
|
status === MigrationStatus.Canceling
|
|
);
|
|
|
|
private canCutoverMigration = (status: string | undefined) => status === MigrationStatus.InProgress;
|
|
|
|
private createSearchAndRefreshContainer(): azdata.FlexContainer {
|
|
this._searchBox = this._view.modelBuilder.inputBox().withProps({
|
|
stopEnterPropagation: true,
|
|
placeHolder: loc.SEARCH_FOR_MIGRATIONS,
|
|
width: '360px'
|
|
}).component();
|
|
|
|
this._disposables.push(this._searchBox.onTextChanged((value) => {
|
|
this.populateMigrationTable();
|
|
}));
|
|
|
|
this._refresh = this._view.modelBuilder.button().withProps({
|
|
iconPath: IconPathHelper.refresh,
|
|
iconHeight: '16px',
|
|
iconWidth: '20px',
|
|
height: '30px',
|
|
label: loc.REFRESH_BUTTON_LABEL,
|
|
}).component();
|
|
|
|
this._disposables.push(
|
|
this._refresh.onDidClick(
|
|
async (e) => { await this.refreshTable(); }));
|
|
|
|
const flexContainer = this._view.modelBuilder.flexContainer().withProps({
|
|
width: 900,
|
|
CSSStyles: {
|
|
'justify-content': 'left'
|
|
},
|
|
}).component();
|
|
|
|
flexContainer.addItem(this._searchBox, {
|
|
flex: '0'
|
|
});
|
|
|
|
this._statusDropdown = this._view.modelBuilder.dropDown().withProps({
|
|
ariaLabel: loc.MIGRATION_STATUS_FILTER,
|
|
values: this._model.statusDropdownValues,
|
|
width: '220px'
|
|
}).component();
|
|
|
|
this._disposables.push(this._statusDropdown.onValueChanged((value) => {
|
|
this.populateMigrationTable();
|
|
}));
|
|
|
|
if (this._filter) {
|
|
this._statusDropdown.value = (<azdata.CategoryValue[]>this._statusDropdown.values).find((value) => {
|
|
return value.name === this._filter;
|
|
});
|
|
}
|
|
|
|
flexContainer.addItem(this._statusDropdown, {
|
|
flex: '0',
|
|
CSSStyles: {
|
|
'margin-left': '20px'
|
|
}
|
|
});
|
|
|
|
flexContainer.addItem(this._refresh, {
|
|
flex: '0',
|
|
CSSStyles: {
|
|
'margin-left': '20px'
|
|
}
|
|
});
|
|
|
|
this._refreshLoader = this._view.modelBuilder.loadingComponent().withProps({
|
|
loading: false,
|
|
height: '55px'
|
|
}).component();
|
|
|
|
flexContainer.addItem(this._refreshLoader, {
|
|
flex: '0 0 auto',
|
|
CSSStyles: {
|
|
'margin-left': '20px'
|
|
}
|
|
});
|
|
this.setAutoRefresh(refreshFrequency);
|
|
const container = this._view.modelBuilder.flexContainer().withProps({
|
|
width: 1000
|
|
}).component();
|
|
container.addItem(flexContainer, {
|
|
flex: '0 0 auto',
|
|
CSSStyles: {
|
|
'width': '980px'
|
|
}
|
|
});
|
|
return container;
|
|
}
|
|
|
|
private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void {
|
|
const classVariable = this;
|
|
clearInterval(this._autoRefreshHandle);
|
|
if (interval !== -1) {
|
|
this._autoRefreshHandle = setInterval(async function () { await classVariable.refreshTable(); }, interval);
|
|
}
|
|
}
|
|
|
|
private registerCommands(): void {
|
|
this._disposables.push(vscode.commands.registerCommand(
|
|
MenuCommands.Cutover,
|
|
async (migrationId: string) => {
|
|
try {
|
|
clearDialogMessage(this._dialogObject);
|
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
|
if (this.canCutoverMigration(migration?.migrationContext.properties.migrationStatus)) {
|
|
const cutoverDialogModel = new MigrationCutoverDialogModel(migration!);
|
|
await cutoverDialogModel.fetchStatus();
|
|
const dialog = new ConfirmCutoverDialog(cutoverDialogModel);
|
|
await dialog.initialize();
|
|
} else {
|
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CUTOVER);
|
|
}
|
|
} catch (e) {
|
|
this._dialogObject.message = {
|
|
text: loc.MIGRATION_STATUS_REFRESH_ERROR,
|
|
description: e.message,
|
|
level: azdata.window.MessageLevel.Error
|
|
};
|
|
|
|
console.log(e);
|
|
}
|
|
}));
|
|
|
|
this._disposables.push(vscode.commands.registerCommand(
|
|
MenuCommands.ViewDatabase,
|
|
async (migrationId: string) => {
|
|
try {
|
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
|
const dialog = new MigrationCutoverDialog(migration!);
|
|
await dialog.initialize();
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
}));
|
|
|
|
this._disposables.push(vscode.commands.registerCommand(
|
|
MenuCommands.ViewTarget,
|
|
async (migrationId: string) => {
|
|
try {
|
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
|
const url = 'https://portal.azure.com/#resource/' + migration!.targetManagedInstance.id;
|
|
await vscode.env.openExternal(vscode.Uri.parse(url));
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
}));
|
|
|
|
this._disposables.push(vscode.commands.registerCommand(
|
|
MenuCommands.ViewService,
|
|
async (migrationId: string) => {
|
|
try {
|
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
|
const dialog = new SqlMigrationServiceDetailsDialog(migration!);
|
|
await dialog.initialize();
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
}));
|
|
|
|
this._disposables.push(vscode.commands.registerCommand(
|
|
MenuCommands.CopyMigration,
|
|
async (migrationId: string) => {
|
|
try {
|
|
clearDialogMessage(this._dialogObject);
|
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
|
const cutoverDialogModel = new MigrationCutoverDialogModel(migration!);
|
|
await cutoverDialogModel.fetchStatus();
|
|
if (cutoverDialogModel.migrationOpStatus) {
|
|
await vscode.env.clipboard.writeText(JSON.stringify({
|
|
'async-operation-details': cutoverDialogModel.migrationOpStatus,
|
|
'details': cutoverDialogModel.migrationStatus
|
|
}, undefined, 2));
|
|
} else {
|
|
await vscode.env.clipboard.writeText(JSON.stringify(cutoverDialogModel.migrationStatus, undefined, 2));
|
|
}
|
|
|
|
await vscode.window.showInformationMessage(loc.DETAILS_COPIED);
|
|
} catch (e) {
|
|
this._dialogObject.message = {
|
|
text: loc.MIGRATION_STATUS_REFRESH_ERROR,
|
|
description: e.message,
|
|
level: azdata.window.MessageLevel.Error
|
|
};
|
|
|
|
console.log(e);
|
|
}
|
|
}));
|
|
|
|
this._disposables.push(vscode.commands.registerCommand(
|
|
MenuCommands.CancelMigration,
|
|
async (migrationId: string) => {
|
|
try {
|
|
clearDialogMessage(this._dialogObject);
|
|
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
|
|
if (this.canCancelMigration(migration?.migrationContext.properties.migrationStatus)) {
|
|
vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, loc.YES, loc.NO).then(async (v) => {
|
|
if (v === loc.YES) {
|
|
const cutoverDialogModel = new MigrationCutoverDialogModel(migration!);
|
|
await cutoverDialogModel.fetchStatus();
|
|
await cutoverDialogModel.cancelMigration();
|
|
}
|
|
});
|
|
} else {
|
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CANCEL);
|
|
}
|
|
} catch (e) {
|
|
this._dialogObject.message = {
|
|
text: loc.MIGRATION_CANCELLATION_ERROR,
|
|
description: e.message,
|
|
level: azdata.window.MessageLevel.Error
|
|
};
|
|
|
|
console.log(e);
|
|
}
|
|
}));
|
|
}
|
|
|
|
private async populateMigrationTable(): Promise<void> {
|
|
try {
|
|
const migrations = filterMigrations(
|
|
this._model._migrations,
|
|
(<azdata.CategoryValue>this._statusDropdown.value).name,
|
|
this._searchBox.value!);
|
|
|
|
migrations.sort((m1, m2) => {
|
|
return new Date(m1.migrationContext.properties?.startedOn) > new Date(m2.migrationContext.properties?.startedOn) ? -1 : 1;
|
|
});
|
|
|
|
const data: azdata.DeclarativeTableCellValue[][] = migrations.map((migration, index) => {
|
|
return [
|
|
{ value: this._getDatabaserHyperLink(migration) },
|
|
{ value: this._getMigrationStatus(migration) },
|
|
{ value: getMigrationMode(migration) },
|
|
{ value: getMigrationTargetType(migration) },
|
|
{ value: migration.targetManagedInstance.name },
|
|
{ value: migration.controller.name },
|
|
{
|
|
value: this._getMigrationDuration(
|
|
migration.migrationContext.properties.startedOn,
|
|
migration.migrationContext.properties.endedOn)
|
|
},
|
|
{ value: this._getMigrationTime(migration.migrationContext.properties.startedOn) },
|
|
{ value: this._getMigrationTime(migration.migrationContext.properties.endedOn) },
|
|
{
|
|
value: {
|
|
commands: this._getMenuCommands(migration),
|
|
context: migration.migrationContext.id
|
|
},
|
|
}
|
|
];
|
|
});
|
|
|
|
await this._statusTable.setDataValues(data);
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
}
|
|
|
|
private _getDatabaserHyperLink(migration: MigrationContext): azdata.FlexContainer {
|
|
const imageControl = this._view.modelBuilder.image()
|
|
.withProps({
|
|
iconPath: IconPathHelper.sqlDatabaseLogo,
|
|
iconHeight: statusImageSize,
|
|
iconWidth: statusImageSize,
|
|
height: statusImageSize,
|
|
width: statusImageSize,
|
|
CSSStyles: imageCellStyles
|
|
})
|
|
.component();
|
|
|
|
const databaseHyperLink = this._view.modelBuilder
|
|
.hyperlink()
|
|
.withProps({
|
|
label: migration.migrationContext.properties.sourceDatabaseName,
|
|
url: '',
|
|
CSSStyles: statusCellStyles
|
|
}).component();
|
|
|
|
this._disposables.push(databaseHyperLink.onDidClick(
|
|
async (e) => await (new MigrationCutoverDialog(migration)).initialize()));
|
|
|
|
return this._view.modelBuilder
|
|
.flexContainer()
|
|
.withItems([imageControl, databaseHyperLink])
|
|
.withProps({ CSSStyles: statusCellStyles, display: 'inline-flex' })
|
|
.component();
|
|
}
|
|
|
|
private _getMigrationTime(migrationTime: string): string {
|
|
return migrationTime
|
|
? new Date(migrationTime).toLocaleString()
|
|
: '---';
|
|
}
|
|
|
|
private _getMigrationDuration(startDate: string, endDate: string): string {
|
|
if (startDate) {
|
|
if (endDate) {
|
|
return convertTimeDifferenceToDuration(
|
|
new Date(startDate),
|
|
new Date(endDate));
|
|
} else {
|
|
return convertTimeDifferenceToDuration(
|
|
new Date(startDate),
|
|
new Date());
|
|
}
|
|
}
|
|
|
|
return '---';
|
|
}
|
|
|
|
private _getMenuCommands(migration: MigrationContext): string[] {
|
|
const menuCommands: string[] = [];
|
|
const migrationStatus = migration?.migrationContext?.properties?.migrationStatus;
|
|
|
|
if (getMigrationMode(migration) === loc.ONLINE &&
|
|
this.canCutoverMigration(migrationStatus)) {
|
|
menuCommands.push(MenuCommands.Cutover);
|
|
}
|
|
|
|
menuCommands.push(...[
|
|
MenuCommands.ViewDatabase,
|
|
MenuCommands.ViewTarget,
|
|
MenuCommands.ViewService,
|
|
MenuCommands.CopyMigration]);
|
|
|
|
if (this.canCancelMigration(migrationStatus)) {
|
|
menuCommands.push(MenuCommands.CancelMigration);
|
|
}
|
|
|
|
return menuCommands;
|
|
}
|
|
|
|
private _getMigrationStatus(migration: MigrationContext): azdata.FlexContainer {
|
|
const properties = migration.migrationContext.properties;
|
|
const migrationStatus = properties.migrationStatus ?? properties.provisioningState;
|
|
let warningCount = 0;
|
|
if (migration.asyncOperationResult?.error?.message) {
|
|
warningCount++;
|
|
}
|
|
if (properties.migrationFailureError?.message) {
|
|
warningCount++;
|
|
}
|
|
if (properties.migrationStatusDetails?.fileUploadBlockingErrors) {
|
|
warningCount += properties.migrationStatusDetails?.fileUploadBlockingErrors.length;
|
|
}
|
|
if (properties.migrationStatusDetails?.restoreBlockingReason) {
|
|
warningCount++;
|
|
}
|
|
|
|
return this._getStatusControl(migrationStatus, warningCount);
|
|
}
|
|
|
|
private _getStatusControl(status: string, count: number): azdata.FlexContainer {
|
|
const control = this._view.modelBuilder
|
|
.flexContainer()
|
|
.withItems([
|
|
// migration status icon
|
|
this._view.modelBuilder.image()
|
|
.withProps({
|
|
iconPath: getMigrationStatusImage(status),
|
|
iconHeight: statusImageSize,
|
|
iconWidth: statusImageSize,
|
|
height: statusImageSize,
|
|
width: statusImageSize,
|
|
CSSStyles: imageCellStyles
|
|
})
|
|
.component(),
|
|
// migration status text
|
|
this._view.modelBuilder.text().withProps({
|
|
value: loc.STATUS_VALUE(status, count),
|
|
height: statusImageSize,
|
|
CSSStyles: statusCellStyles,
|
|
}).component()
|
|
])
|
|
.withProps({ CSSStyles: statusCellStyles, display: 'inline-flex' })
|
|
.component();
|
|
|
|
if (count > 0) {
|
|
control.addItems([
|
|
// migration warning / error image
|
|
this._view.modelBuilder.image().withProps({
|
|
iconPath: this._statusInfoMap(status),
|
|
iconHeight: statusImageSize,
|
|
iconWidth: statusImageSize,
|
|
height: statusImageSize,
|
|
width: statusImageSize,
|
|
CSSStyles: imageCellStyles
|
|
}).component(),
|
|
// migration warning / error counts
|
|
this._view.modelBuilder.text().withProps({
|
|
value: loc.STATUS_WARNING_COUNT(status, count),
|
|
height: statusImageSize,
|
|
CSSStyles: statusCellStyles,
|
|
}).component()
|
|
]);
|
|
}
|
|
|
|
return control;
|
|
}
|
|
|
|
private async refreshTable(): Promise<void> {
|
|
if (this.isRefreshing) {
|
|
return;
|
|
}
|
|
|
|
this.isRefreshing = true;
|
|
try {
|
|
clearDialogMessage(this._dialogObject);
|
|
this._refreshLoader.loading = true;
|
|
const currentConnection = await azdata.connection.getCurrentConnection();
|
|
this._model._migrations = await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true);
|
|
await this.populateMigrationTable();
|
|
} catch (e) {
|
|
this._dialogObject.message = {
|
|
text: loc.MIGRATION_STATUS_REFRESH_ERROR,
|
|
description: e.message,
|
|
level: azdata.window.MessageLevel.Error
|
|
};
|
|
|
|
console.log(e);
|
|
} finally {
|
|
this.isRefreshing = false;
|
|
this._refreshLoader.loading = false;
|
|
}
|
|
}
|
|
|
|
private createStatusTable(): azdata.DeclarativeTableComponent {
|
|
const rowCssStyle: azdata.CssStyles = {
|
|
'border': 'none',
|
|
'text-align': 'left',
|
|
'border-bottom': '1px solid',
|
|
};
|
|
|
|
const headerCssStyles: azdata.CssStyles = {
|
|
'border': 'none',
|
|
'text-align': 'left',
|
|
'border-bottom': '1px solid',
|
|
'font-weight': 'bold',
|
|
'padding-left': '0px',
|
|
'padding-right': '0px'
|
|
};
|
|
|
|
this._statusTable = this._view.modelBuilder.declarativeTable().withProps({
|
|
ariaLabel: loc.MIGRATION_STATUS,
|
|
columns: [
|
|
{
|
|
displayName: loc.DATABASE,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
width: '90px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.MIGRATION_STATUS,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
width: '170px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.MIGRATION_MODE,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
width: '90px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.AZURE_SQL_TARGET,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
width: '130px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.TARGET_AZURE_SQL_INSTANCE_NAME,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
width: '130px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.DATABASE_MIGRATION_SERVICE,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
width: '150px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.DURATION,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
width: '55px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.START_TIME,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
width: '140px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: loc.FINISH_TIME,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
width: '140px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles
|
|
},
|
|
{
|
|
displayName: '',
|
|
valueType: azdata.DeclarativeDataType.menu,
|
|
width: '20px',
|
|
isReadOnly: true,
|
|
rowCssStyles: rowCssStyle,
|
|
headerCssStyles: headerCssStyles,
|
|
}
|
|
]
|
|
}).component();
|
|
return this._statusTable;
|
|
}
|
|
|
|
private _statusInfoMap(status: string): azdata.IconPath {
|
|
return status === MigrationStatus.InProgress
|
|
|| status === MigrationStatus.Creating
|
|
|| status === MigrationStatus.Completing
|
|
? IconPathHelper.warning
|
|
: IconPathHelper.error;
|
|
}
|
|
}
|