mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 17:23:19 -05:00
Adding migration status and cutover to extension (#14482)
This commit is contained in:
@@ -7,6 +7,7 @@ import * as azdata from 'azdata';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import { SqlDatabaseTree } from './sqlDatabasesTree';
|
||||
import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
|
||||
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
|
||||
|
||||
export type Issues = {
|
||||
description: string,
|
||||
@@ -30,7 +31,7 @@ export class AssessmentResultsDialog {
|
||||
private _tree: SqlDatabaseTree;
|
||||
|
||||
|
||||
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string) {
|
||||
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private skuRecommendationPage: SKURecommendationPage) {
|
||||
this._model = model;
|
||||
let assessmentData = this.parseData(this._model);
|
||||
this._tree = new SqlDatabaseTree(this._model, assessmentData);
|
||||
@@ -126,6 +127,7 @@ export class AssessmentResultsDialog {
|
||||
|
||||
protected async execute() {
|
||||
this.model._migrationDbs = this._tree.selectedDbs();
|
||||
this.skuRecommendationPage.refreshDatabaseCount(this._model._migrationDbs.length);
|
||||
this._isOpen = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { createMigrationController, getMigrationControllerRegions, getMigrationController, getResourceGroups, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../../api/azure';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import * as constants from '../../models/strings';
|
||||
import * as constants from '../../constants/strings';
|
||||
import * as os from 'os';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { IntergrationRuntimePage } from '../../wizard/integrationRuntimePage';
|
||||
@@ -130,7 +130,6 @@ export class CreateMigrationControllerDialog {
|
||||
this._dialogObject.okButton.enabled = false;
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
this._dialogObject.cancelButton.onClick((e) => {
|
||||
this.migrationStateModel._migrationController = undefined!;
|
||||
});
|
||||
this._dialogObject.okButton.onClick((e) => {
|
||||
this.irPage.populateMigrationController();
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
|
||||
import * as loc from '../../constants/strings';
|
||||
export class MigrationCutoverDialog {
|
||||
private _dialogObject!: azdata.window.Dialog;
|
||||
private _view!: azdata.ModelView;
|
||||
private _model: MigrationCutoverDialogModel;
|
||||
|
||||
private _databaseTitleName!: azdata.TextComponent;
|
||||
private _databaseCutoverButton!: azdata.ButtonComponent;
|
||||
private _refresh!: azdata.ButtonComponent;
|
||||
|
||||
private _serverName!: azdata.TextComponent;
|
||||
private _serverVersion!: azdata.TextComponent;
|
||||
private _targetServer!: azdata.TextComponent;
|
||||
private _targetVersion!: azdata.TextComponent;
|
||||
private _migrationStatus!: azdata.TextComponent;
|
||||
private _fullBackupFile!: azdata.TextComponent;
|
||||
private _lastAppliedLSN!: azdata.TextComponent;
|
||||
private _lastAppliedBackupFile!: azdata.TextComponent;
|
||||
private _lastAppliedBackupTakenOn!: azdata.TextComponent;
|
||||
|
||||
private _fileCount!: azdata.TextComponent;
|
||||
|
||||
private fileTable!: azdata.TableComponent;
|
||||
|
||||
private _startCutover!: boolean;
|
||||
|
||||
constructor(migration: MigrationContext) {
|
||||
this._model = new MigrationCutoverDialogModel(migration);
|
||||
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_CUTOVER, 'MigrationCutoverDialog', 1000);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
let tab = azdata.window.createTab('');
|
||||
tab.registerContent(async (view: azdata.ModelView) => {
|
||||
this._view = view;
|
||||
const sourceDetails = this.createInfoField(loc.SOURCE_VERSION, '');
|
||||
const sourceVersion = this.createInfoField(loc.SOURCE_VERSION, '');
|
||||
|
||||
this._serverName = sourceDetails.text;
|
||||
this._serverVersion = sourceVersion.text;
|
||||
|
||||
const flexServer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
flexServer.addItem(sourceDetails.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '150px'
|
||||
}
|
||||
});
|
||||
flexServer.addItem(sourceVersion.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '150px'
|
||||
}
|
||||
});
|
||||
|
||||
const targetServer = this.createInfoField(loc.TARGET_SERVER, '');
|
||||
const targetVersion = this.createInfoField(loc.TARGET_VERSION, '');
|
||||
|
||||
this._targetServer = targetServer.text;
|
||||
this._targetVersion = targetVersion.text;
|
||||
|
||||
const flexTarget = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
flexTarget.addItem(targetServer.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
flexTarget.addItem(targetVersion.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
|
||||
const migrationStatus = this.createInfoField(loc.MIGRATION_STATUS, '');
|
||||
const fullBackupFileOn = this.createInfoField(loc.FULL_BACKUP_FILES, '');
|
||||
|
||||
|
||||
this._migrationStatus = migrationStatus.text;
|
||||
this._fullBackupFile = fullBackupFileOn.text;
|
||||
|
||||
const flexStatus = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
flexStatus.addItem(migrationStatus.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '180px'
|
||||
}
|
||||
});
|
||||
flexStatus.addItem(fullBackupFileOn.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '180px'
|
||||
}
|
||||
});
|
||||
|
||||
const lastSSN = this.createInfoField(loc.LAST_APPLIED_LSN, '');
|
||||
const lastAppliedBackup = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
||||
const lastAppliedBackupOn = this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '');
|
||||
|
||||
this._lastAppliedLSN = lastSSN.text;
|
||||
this._lastAppliedBackupFile = lastAppliedBackup.text;
|
||||
this._lastAppliedBackupTakenOn = lastAppliedBackupOn.text;
|
||||
|
||||
const flexFile = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
flexFile.addItem(lastSSN.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
flexFile.addItem(lastAppliedBackup.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
flexFile.addItem(lastAppliedBackupOn.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
const flexInfo = view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'width': '700px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
flexInfo.addItem(flexServer, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '150px'
|
||||
}
|
||||
});
|
||||
|
||||
flexInfo.addItem(flexTarget, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
|
||||
flexInfo.addItem(flexStatus, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '180px'
|
||||
}
|
||||
});
|
||||
|
||||
flexInfo.addItem(flexFile, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': '200px'
|
||||
}
|
||||
});
|
||||
|
||||
this._fileCount = view.modelBuilder.text().withProps({
|
||||
width: '500px',
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold'
|
||||
}
|
||||
}).component();
|
||||
|
||||
this.fileTable = view.modelBuilder.table().withProps({
|
||||
columns: [
|
||||
{
|
||||
value: loc.ACTIVE_BACKUP_FILES,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: loc.TYPE,
|
||||
width: 100,
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: loc.STATUS,
|
||||
width: 100,
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: loc.BACKUP_START_TIME,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
}, {
|
||||
value: loc.FIRST_LSN,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
}, {
|
||||
value: loc.LAST_LSN,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
width: '800px',
|
||||
height: '600px',
|
||||
}).component();
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: await this.migrationContainerHeader()
|
||||
},
|
||||
{
|
||||
component: flexInfo
|
||||
},
|
||||
{
|
||||
component: this._fileCount
|
||||
},
|
||||
{
|
||||
component: this.fileTable
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
this._dialogObject.content = [tab];
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
this.refreshStatus();
|
||||
}
|
||||
|
||||
|
||||
private migrationContainerHeader(): azdata.FlexContainer {
|
||||
const header = this._view.modelBuilder.flexContainer().withLayout({
|
||||
}).component();
|
||||
|
||||
this._databaseTitleName = this._view.modelBuilder.text().withProps({
|
||||
CSSStyles: {
|
||||
'font-size': 'large',
|
||||
'width': '400px'
|
||||
},
|
||||
value: this._model._migration.migrationContext.name
|
||||
}).component();
|
||||
|
||||
header.addItem(this._databaseTitleName, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '500px'
|
||||
}
|
||||
});
|
||||
|
||||
this._databaseCutoverButton = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.cutover,
|
||||
iconHeight: '14px',
|
||||
iconWidth: '12px',
|
||||
label: 'Start Cutover',
|
||||
height: '55px',
|
||||
width: '100px',
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this._databaseCutoverButton.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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
header.addItem(this._databaseCutoverButton, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '100px'
|
||||
}
|
||||
});
|
||||
|
||||
this._refresh = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.refresh,
|
||||
iconHeight: '16px',
|
||||
iconWidth: '16px',
|
||||
label: 'Refresh',
|
||||
height: '55px',
|
||||
width: '100px'
|
||||
}).component();
|
||||
|
||||
this._refresh.onDidClick((e) => {
|
||||
this.refreshStatus();
|
||||
});
|
||||
|
||||
header.addItem(this._refresh, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '100px'
|
||||
}
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
private async refreshStatus(): Promise<void> {
|
||||
try {
|
||||
await this._model.fetchStatus();
|
||||
const sqlServerInfo = await azdata.connection.getServerInfo(this._model._migration.sourceConnectionProfile.connectionId);
|
||||
const sqlServerName = this._model._migration.sourceConnectionProfile.serverName;
|
||||
const sqlServerVersion = sqlServerInfo.serverVersion;
|
||||
const sqlServerEdition = sqlServerInfo.serverEdition;
|
||||
const targetServerName = this._model._migration.targetManagedInstance.name;
|
||||
let targetServerVersion;
|
||||
if (this._model.migrationStatus.id.includes('managedInstances')) {
|
||||
targetServerVersion = loc.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
|
||||
} else {
|
||||
targetServerVersion = loc.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
|
||||
}
|
||||
|
||||
const migrationStatusTextValue = this._model.migrationStatus.properties.migrationStatus;
|
||||
|
||||
let fullBackupFileName: string;
|
||||
let lastAppliedSSN: string;
|
||||
let lastAppliedBackupFileTakenOn: string;
|
||||
|
||||
|
||||
const tableData: ActiveBackupFileSchema[] = [];
|
||||
|
||||
this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.forEach((activeBackupSet) => {
|
||||
tableData.push(
|
||||
{
|
||||
fileName: activeBackupSet.listOfBackupFiles[0].fileName,
|
||||
type: activeBackupSet.backupType,
|
||||
status: activeBackupSet.listOfBackupFiles[0].status,
|
||||
backupStartTime: activeBackupSet.backupStartDate,
|
||||
firstLSN: activeBackupSet.firstLSN,
|
||||
lastLSN: activeBackupSet.lastLSN
|
||||
}
|
||||
);
|
||||
if (activeBackupSet.listOfBackupFiles[0].fileName.substr(activeBackupSet.listOfBackupFiles[0].fileName.lastIndexOf('.') + 1) === 'bak') {
|
||||
fullBackupFileName = activeBackupSet.listOfBackupFiles[0].fileName;
|
||||
}
|
||||
if (activeBackupSet.listOfBackupFiles[0].fileName === this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename) {
|
||||
lastAppliedSSN = activeBackupSet.lastLSN;
|
||||
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
||||
}
|
||||
});
|
||||
|
||||
this._serverName.value = sqlServerName;
|
||||
this._serverVersion.value = `${sqlServerVersion}
|
||||
${sqlServerEdition}`;
|
||||
|
||||
this._targetServer.value = targetServerName;
|
||||
this._targetVersion.value = targetServerVersion;
|
||||
|
||||
this._migrationStatus.value = migrationStatusTextValue;
|
||||
this._fullBackupFile.value = fullBackupFileName!;
|
||||
|
||||
this._lastAppliedLSN.value = lastAppliedSSN!;
|
||||
this._lastAppliedBackupFile.value = this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename;
|
||||
this._lastAppliedBackupTakenOn.value = new Date(lastAppliedBackupFileTakenOn!).toLocaleString();
|
||||
|
||||
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
|
||||
|
||||
this.fileTable.data = tableData.map((row) => {
|
||||
return [
|
||||
row.fileName,
|
||||
row.type,
|
||||
row.status,
|
||||
new Date(row.backupStartTime).toLocaleString(),
|
||||
row.firstLSN,
|
||||
row.lastLSN
|
||||
];
|
||||
});
|
||||
if (this._model.migrationStatus.properties.migrationStatusDetails?.isFullBackupRestored) {
|
||||
this._startCutover = true;
|
||||
}
|
||||
|
||||
if (migrationStatusTextValue === 'InProgress') {
|
||||
this._databaseCutoverButton.enabled = true;
|
||||
} else {
|
||||
this._databaseCutoverButton.enabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private createInfoField(label: string, value: string): {
|
||||
flexContainer: azdata.FlexContainer,
|
||||
text: azdata.TextComponent
|
||||
} {
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
const labelComponent = this._view.modelBuilder.text().withProps({
|
||||
value: label,
|
||||
CSSStyles: {
|
||||
'font-weight': 'bold',
|
||||
'margin-bottom': '0'
|
||||
}
|
||||
}).component();
|
||||
flexContainer.addItem(labelComponent);
|
||||
|
||||
const textComponent = this._view.modelBuilder.text().withProps({
|
||||
value: value,
|
||||
CSSStyles: {
|
||||
'margin-top': '5px',
|
||||
'margin-bottom': '0'
|
||||
}
|
||||
}).component();
|
||||
flexContainer.addItem(textComponent);
|
||||
return {
|
||||
flexContainer: flexContainer,
|
||||
text: textComponent
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface ActiveBackupFileSchema {
|
||||
fileName: string,
|
||||
type: string,
|
||||
status: string,
|
||||
backupStartTime: string,
|
||||
firstLSN: string,
|
||||
lastLSN: string
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getMigrationStatus, DatabaseMigration, startMigrationCutover } from '../../api/azure';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
|
||||
|
||||
export class MigrationCutoverDialogModel {
|
||||
|
||||
public migrationStatus!: DatabaseMigration;
|
||||
|
||||
constructor(public _migration: MigrationContext) {
|
||||
}
|
||||
|
||||
public async fetchStatus(): Promise<void> {
|
||||
this.migrationStatus = (await getMigrationStatus(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this._migration.migrationContext
|
||||
));
|
||||
}
|
||||
|
||||
public async startCutover(): Promise<DatabaseMigration | undefined> {
|
||||
try {
|
||||
if (this.migrationStatus) {
|
||||
return await startMigrationCutover(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this.migrationStatus
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return undefined!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 } from '../../models/migrationLocalStorage';
|
||||
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
|
||||
import { MigrationCategory, MigrationStatusDialogModel } from './migrationStatusDialogModel';
|
||||
import * as loc from '../../constants/strings';
|
||||
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;
|
||||
|
||||
constructor(migrations: MigrationContext[], private _filter: MigrationCategory) {
|
||||
this._model = new MigrationStatusDialogModel(migrations);
|
||||
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide');
|
||||
}
|
||||
|
||||
initialize() {
|
||||
let tab = azdata.window.createTab('');
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
this._view = view;
|
||||
|
||||
this._statusDropdown = this._view.modelBuilder.dropDown().withProps({
|
||||
values: this._model.statusDropdownValues,
|
||||
width: '220px'
|
||||
}).component();
|
||||
|
||||
this._statusDropdown.onValueChanged((value) => {
|
||||
this.populateMigrationTable();
|
||||
});
|
||||
|
||||
this._statusDropdown.value = this._statusDropdown.values![this._filter];
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.createSearchAndRefreshContainer()
|
||||
},
|
||||
{
|
||||
component: this._statusDropdown
|
||||
},
|
||||
{
|
||||
component: this.createStatusTable()
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
this._dialogObject.content = [tab];
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
}
|
||||
|
||||
private createSearchAndRefreshContainer(): azdata.FlexContainer {
|
||||
this._searchBox = this._view.modelBuilder.inputBox().withProps({
|
||||
placeHolder: loc.SEARCH_FOR_MIGRATIONS,
|
||||
width: '360px'
|
||||
}).component();
|
||||
|
||||
this._searchBox.onTextChanged((value) => {
|
||||
this.populateMigrationTable();
|
||||
});
|
||||
|
||||
this._refresh = this._view.modelBuilder.button().withProps({
|
||||
iconPath: {
|
||||
light: IconPathHelper.refresh.light,
|
||||
dark: IconPathHelper.refresh.dark
|
||||
},
|
||||
iconHeight: '16px',
|
||||
iconWidth: '16px',
|
||||
height: '30px',
|
||||
label: 'Refresh',
|
||||
}).component();
|
||||
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().component();
|
||||
|
||||
flexContainer.addItem(this._searchBox, {
|
||||
flex: '0'
|
||||
});
|
||||
|
||||
flexContainer.addItem(this._refresh, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'margin-left': '20px'
|
||||
}
|
||||
});
|
||||
|
||||
return flexContainer;
|
||||
}
|
||||
|
||||
private populateMigrationTable(): void {
|
||||
|
||||
try {
|
||||
const migrations = this._model.filterMigration(
|
||||
this._searchBox.value!,
|
||||
(<azdata.CategoryValue>this._statusDropdown.value).name
|
||||
);
|
||||
|
||||
const data: azdata.DeclarativeTableCellValue[][] = [];
|
||||
|
||||
migrations.forEach((migration) => {
|
||||
const migrationRow: azdata.DeclarativeTableCellValue[] = [];
|
||||
|
||||
const databaseHyperLink = this._view.modelBuilder.hyperlink().withProps({
|
||||
label: migration.migrationContext.name,
|
||||
url: ''
|
||||
}).component();
|
||||
databaseHyperLink.onDidClick(async (e) => {
|
||||
await (new MigrationCutoverDialog(migration)).initialize();
|
||||
});
|
||||
migrationRow.push({
|
||||
value: databaseHyperLink,
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: migration.migrationContext.properties.migrationStatus
|
||||
});
|
||||
|
||||
const sqlMigrationIcon = this._view.modelBuilder.image().withProps({
|
||||
iconPath: IconPathHelper.sqlMigrationLogo,
|
||||
iconWidth: '16px',
|
||||
iconHeight: '16px',
|
||||
width: '32px',
|
||||
height: '20px'
|
||||
}).component();
|
||||
const sqlMigrationName = this._view.modelBuilder.hyperlink().withProps({
|
||||
label: migration.migrationContext.name,
|
||||
url: ''
|
||||
}).component();
|
||||
sqlMigrationName.onDidClick((e) => {
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
});
|
||||
|
||||
const sqlMigrationContainer = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'justify-content': 'center'
|
||||
}
|
||||
}).component();
|
||||
sqlMigrationContainer.addItem(sqlMigrationIcon, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '32px'
|
||||
}
|
||||
});
|
||||
sqlMigrationContainer.addItem(sqlMigrationName,
|
||||
{
|
||||
CSSStyles: {
|
||||
'width': 'auto'
|
||||
}
|
||||
});
|
||||
migrationRow.push({
|
||||
value: sqlMigrationContainer
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: loc.ONLINE
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: '---'
|
||||
});
|
||||
migrationRow.push({
|
||||
value: '---'
|
||||
});
|
||||
|
||||
data.push(migrationRow);
|
||||
});
|
||||
|
||||
this._statusTable.dataValues = data;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private createStatusTable(): azdata.DeclarativeTableComponent {
|
||||
this._statusTable = this._view.modelBuilder.declarativeTable().withProps({
|
||||
columns: [
|
||||
{
|
||||
displayName: loc.DATABASE,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
width: '100px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.MIGRATION_STATUS,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.TARGET_AZURE_SQL_INSTANCE_NAME,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
width: '300px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.CUTOVER_TYPE,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '100px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.START_TIME,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: loc.FINISH_TIME,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
}
|
||||
]
|
||||
}).component();
|
||||
return this._statusTable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
|
||||
export class MigrationStatusDialogModel {
|
||||
|
||||
public statusDropdownValues: azdata.CategoryValue[] = [
|
||||
{
|
||||
displayName: 'Status: All',
|
||||
name: 'All',
|
||||
}, {
|
||||
displayName: 'Status: Ongoing',
|
||||
name: 'Ongoing',
|
||||
}, {
|
||||
displayName: 'Status: Succeeded',
|
||||
name: 'Succeeded',
|
||||
}
|
||||
];
|
||||
|
||||
constructor(public _migrations: MigrationContext[]) {
|
||||
}
|
||||
|
||||
public filterMigration(databaseName: string, category: string): MigrationContext[] {
|
||||
let filteredMigration: MigrationContext[] = [];
|
||||
if (category === 'All') {
|
||||
filteredMigration = this._migrations;
|
||||
} else if (category === 'Ongoing') {
|
||||
filteredMigration = this._migrations.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
return status === 'InProgress' || status === 'Creating' || status === 'Completing';
|
||||
});
|
||||
} else if (category === 'Succeeded') {
|
||||
filteredMigration = this._migrations.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
return status === 'Succeeded';
|
||||
});
|
||||
}
|
||||
if (databaseName) {
|
||||
filteredMigration = filteredMigration.filter((value) => {
|
||||
return value.migrationContext.name.toLowerCase().includes(databaseName.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
return filteredMigration;
|
||||
}
|
||||
}
|
||||
|
||||
export enum MigrationCategory {
|
||||
ALL,
|
||||
ONGOING,
|
||||
SUCCEEDED
|
||||
}
|
||||
Reference in New Issue
Block a user