mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-03 01:25:38 -05:00
Surfacing migration errors in dashboard (#14956)
* vbumping migration * Adding 2 new icons cancel and warning * Fixed help link display text in assessments * Adding summary page redesign and resource name validations * Made headings bold * Fixed sku recommendation page styling Added check item for assessment * Validating account dropdown after token refresh * Renamed cutover to mode * cutover to mode renaming changes. * Converting to details api for more warnings * Added target database name and fixed cancel icon * Surfacing warning info in dashboard. * Consolidated fetch migrations logic Localilzed some strings Surface migration errors in dashboard and status page Table redesign in status dialog Fixed a major bug that happens when multiple dashboards are opened due to class variable sharing * removing console count * Fixing regex for SQL MI database names * Allowing spaces in regex
This commit is contained in:
@@ -27,6 +27,8 @@ export class IconPathHelper {
|
||||
public static sqlServerLogo: IconPath;
|
||||
public static sqlDatabaseLogo: IconPath;
|
||||
public static sqlDatabaseWarningLogo: IconPath;
|
||||
public static cancel: IconPath;
|
||||
public static warning: IconPath;
|
||||
|
||||
public static setExtensionContext(context: vscode.ExtensionContext) {
|
||||
IconPathHelper.copy = {
|
||||
@@ -93,5 +95,13 @@ export class IconPathHelper {
|
||||
light: context.asAbsolutePath('images/sqlDatabaseWarning.svg'),
|
||||
dark: context.asAbsolutePath('images/sqlDatabaseWarning.svg')
|
||||
};
|
||||
IconPathHelper.cancel = {
|
||||
light: context.asAbsolutePath('images/cancel.svg'),
|
||||
dark: context.asAbsolutePath('images/cancel.svg')
|
||||
};
|
||||
IconPathHelper.warning = {
|
||||
light: context.asAbsolutePath('images/warning.svg'),
|
||||
dark: context.asAbsolutePath('images/warning.svg')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ export function TARGET_BLOB_CONTAINER(dbName: string): string {
|
||||
export const ENTER_NETWORK_SHARE_INFORMATION = localize('sql.migration.enter.network.share.information', "Enter target names for selected source database(s)");
|
||||
export const ENTER_BLOB_CONTAINER_INFORMATION = localize('sql.migration.blob.container.information', "Enter the target name and select the blob container location for selected databases");
|
||||
export const ENTER_FILE_SHARE_INFORMATION = localize('sql.migration.enter.file.share.information', "Enter the target name and select the file share location of selected databases");
|
||||
export const INVALID_TARGET_NAME_ERROR = localize('sql.migration.invalid.target.name.error', "Please enter a valid name for the target database.");
|
||||
|
||||
// integration runtime page
|
||||
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service");
|
||||
@@ -212,8 +213,8 @@ export const SUMMARY_PAGE_TITLE = localize('sql.migration.summary.page.title', "
|
||||
export const AZURE_ACCOUNT_LINKED = localize('sql.migration.summary.azure.account.linked', "Azure account linked");
|
||||
export const MIGRATION_TARGET = localize('sql.migration.summary.migration.target', "Migration target");
|
||||
export const SUMMARY_MI_TYPE = localize('sql.migration.summary.mi.type', "Azure SQL Managed Instance");
|
||||
export const SUMMARY_VM_TYPE = localize('sql.migration.summary.vm.type', "Azure SQL Virtual Machine");
|
||||
export const SUMMARY_DATABASE_COUNT_LABEL = localize('sql.migration.summary.database.count', "Number of database to be migrated");
|
||||
export const SUMMARY_VM_TYPE = localize('sql.migration.summary.vm.type', "SQL Server on Azure Virtual Machine");
|
||||
export const SUMMARY_DATABASE_COUNT_LABEL = localize('sql.migration.summary.database.count', "Database(s) to be migrated");
|
||||
export const SUMMARY_AZURE_STORAGE_SUBSCRIPTION = localize('sql.migration.summary.azure.storage.subscription', "Azure storage subscription");
|
||||
export const SUMMARY_AZURE_STORAGE = localize('sql.migration.summary.azure.storage', "Azure storage");
|
||||
export const SUMMARY_IR_NODE = localize('sql.migration.ir.node', "Integration Runtime node");
|
||||
@@ -221,6 +222,11 @@ export const NETWORK_SHARE = localize('sql.migration.network.share', "Network Sh
|
||||
export const BLOB_CONTAINER = localize('sql.migration.blob.container.title', "Blob Container");
|
||||
export const FILE_SHARE = localize('sql.migration.file.share.title', "File Share");
|
||||
export const MIGRATION_STARTED = localize('sql.migration.started.notification', "Migration in progress");
|
||||
export const SOURCE_DATABASES = localize('sql.migration.source.databases', "Source Database(s)");
|
||||
export const MODE = localize('sql.migration.mode', "Mode");
|
||||
export const BACKUP_LOCATION = localize('sql.migration.backup.location', "Backup Location");
|
||||
export const AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS = localize('sql.migration.azure.storage.account.to.upload.backups', "Azure Storage Account to Upload Backups");
|
||||
export const SHIR = localize('sql.migration.shir', "Self-hosted Integration Runtime node");
|
||||
|
||||
// Open notebook quick pick string
|
||||
export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pick.placeholder', "Select the operation you'd like to perform");
|
||||
@@ -243,13 +249,21 @@ export const PRE_REQ_TITLE = localize('sql.migration.pre.req.title', "Things you
|
||||
export const PRE_REQ_1 = localize('sql.migration.pre.req.1', "Azure account details");
|
||||
export const PRE_REQ_2 = localize('sql.migration.pre.req.2', "Azure SQL Managed Instance or SQL Server on Azure Virtual Machine");
|
||||
export const PRE_REQ_3 = localize('sql.migration.pre.req.3', "Backup location details");
|
||||
export const MIGRATION_IN_PROGRESS = localize('sql.migration.migration.in.progress', "Migration in progress");
|
||||
export const MIGRATION_IN_PROGRESS = localize('sql.migration.migration.in.progress', "Database migration in progress");
|
||||
export const LOG_SHIPPING_IN_PROGRESS = localize('sql.migration.log.shipping.in.progress', "Log shipping in progress");
|
||||
export const MIGRATION_COMPLETED = localize('sql.migration.migration.completed', "Migration completed");
|
||||
export const MIGRATION_COMPLETED = localize('sql.migration.migration.completed', "Database migration completed");
|
||||
export const SUCCESSFULLY_MIGRATED_TO_AZURE_SQL = localize('sql.migration.successfully.migrated.to.azure.sql', "Successfully migrated to Azure SQL");
|
||||
export const MIGRATION_NOT_STARTED = localize('sql.migration.migration.not.started', "Migration not started");
|
||||
export const CHOOSE_TO_MIGRATE_TO_AZURE_SQL = localize('sql.migration.choose.to.migrate.to.azure.sql', "Choose to migrate to Azure SQL");
|
||||
export const COMING_SOON = localize('sql.migration.coming.soon', "Coming soon");
|
||||
export function MIGRATION_INPROGRESS_WARNING(count: number) {
|
||||
switch (count) {
|
||||
case 1:
|
||||
return localize('sql.migration.inprogress.warning.single', "{0} database has warnings", count);
|
||||
default:
|
||||
return localize('sql.migration.inprogress.warning.multiple', "{0} databases have warnings", count);
|
||||
}
|
||||
}
|
||||
|
||||
// Azure APIs
|
||||
export const EASTUS2EUAP = localize('sql.migration.eastus2euap', 'East US 2 EUAP');
|
||||
@@ -259,6 +273,7 @@ export const MIGRATION_CUTOVER = localize('sql.migration.cutover', "Migration cu
|
||||
export const SOURCE_DATABASE = localize('sql.migration.source.database', "Source database");
|
||||
export const SOURCE_SERVER = localize('sql.migration.source.server', "Source server");
|
||||
export const SOURCE_VERSION = localize('sql.migration.source.version', "Source version");
|
||||
export const TARGET_DATABASE_NAME = localize('sql.migration.target.database.name', "Target database name");
|
||||
export const TARGET_SERVER = localize('sql.migration.target.server', "Target server");
|
||||
export const TARGET_VERSION = localize('sql.migration.target.version', "Target version");
|
||||
export const MIGRATION_STATUS = localize('sql.migration.migration.status', "Migration status");
|
||||
@@ -295,6 +310,28 @@ export const TARGET_AZURE_SQL_INSTANCE_NAME = localize('sql.migration.target.azu
|
||||
export const MIGRATION_MODE = localize('sql.migration.cutover.type', "Migration Mode");
|
||||
export const START_TIME = localize('sql.migration.start.time', "Start Time");
|
||||
export const FINISH_TIME = localize('sql.migration.finish.time', "Finish Time");
|
||||
export function STATUS_WARNING_COUNT(status: string, count: number): string {
|
||||
if (status === 'InProgress' || status === 'Creating' || status === 'Completing' || status === 'Creating') {
|
||||
switch (count) {
|
||||
case 0:
|
||||
return localize('sql.migration.status.warning.count.none', "{0}", status);
|
||||
case 1:
|
||||
return localize('sql.migration.status.warning.count.single', "{0} ({1} Warning)", status, count);
|
||||
default:
|
||||
return localize('sql.migration.status.warning.count.multiple', "{0} ({1} Warnings)", status, count);
|
||||
}
|
||||
} else {
|
||||
switch (count) {
|
||||
case 0:
|
||||
return localize('sql.migration.status.error.count.none', "{0}", status);
|
||||
case 1:
|
||||
return localize('sql.migration.status.error.count.single', "{0} ({1} Error)", status, count);
|
||||
default:
|
||||
return localize('sql.migration.status.error.count.multiple', "{0} ({1} Errors)", status, count);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Source Credentials page.
|
||||
export const SOURCE_CONFIGURATION = localize('sql.migration.source.configuration', "Source Configuration");
|
||||
|
||||
@@ -23,7 +23,10 @@ const maxWidth = 800;
|
||||
|
||||
interface StatusCard {
|
||||
container: azdata.DivContainer;
|
||||
count: azdata.TextComponent
|
||||
count: azdata.TextComponent,
|
||||
textContainer?: azdata.FlexContainer,
|
||||
warningContainer?: azdata.FlexContainer,
|
||||
warningText?: azdata.TextComponent,
|
||||
}
|
||||
|
||||
export class DashboardWidget {
|
||||
@@ -34,10 +37,10 @@ export class DashboardWidget {
|
||||
|
||||
|
||||
private _inProgressMigrationButton!: StatusCard;
|
||||
private _inProgressWarningMigrationButton!: StatusCard;
|
||||
private _successfulMigrationButton!: StatusCard;
|
||||
private _notStartedMigrationCard!: StatusCard;
|
||||
private _migrationStatus!: MigrationContext[];
|
||||
|
||||
private _migrationStatusMap: Map<string, MigrationContext[]> = new Map();
|
||||
private _viewAllMigrationsButton!: azdata.ButtonComponent;
|
||||
|
||||
constructor() {
|
||||
@@ -46,6 +49,16 @@ export class DashboardWidget {
|
||||
});
|
||||
}
|
||||
|
||||
private async getCurrentMigrations(): Promise<MigrationContext[]> {
|
||||
const connectionId = (await azdata.connection.getCurrentConnection()).connectionId;
|
||||
return this._migrationStatusMap.get(connectionId)!;
|
||||
}
|
||||
|
||||
private async setCurrentMigrations(migrations: MigrationContext[]): Promise<void> {
|
||||
const connectionId = (await azdata.connection.getCurrentConnection()).connectionId;
|
||||
this._migrationStatusMap.set(connectionId, migrations);
|
||||
}
|
||||
|
||||
public register(): void {
|
||||
azdata.ui.registerModelViewProvider('migration.dashboard', async (view) => {
|
||||
this._view = view;
|
||||
@@ -199,7 +212,7 @@ export class DashboardWidget {
|
||||
height: maxHeight,
|
||||
iconHeight: 32,
|
||||
iconPath: taskMetaData.iconPath,
|
||||
iconWidth: 32,
|
||||
iconWidth: 36,
|
||||
label: taskMetaData.title,
|
||||
title: taskMetaData.title,
|
||||
width: maxWidth,
|
||||
@@ -219,26 +232,47 @@ export class DashboardWidget {
|
||||
this._viewAllMigrationsButton.enabled = false;
|
||||
this._migrationStatusCardLoadingContainer.loading = true;
|
||||
try {
|
||||
this._migrationStatus = await this.getMigrations();
|
||||
|
||||
const inProgressMigrations = this._migrationStatus.filter((value) => {
|
||||
this.setCurrentMigrations(await this.getMigrations());
|
||||
const migrationStatus = await this.getCurrentMigrations();
|
||||
const inProgressMigrations = migrationStatus.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
const provisioning = value.migrationContext.properties.provisioningState;
|
||||
return status === 'InProgress' || status === 'Creating' || status === 'Completing' || provisioning === 'Creating';
|
||||
});
|
||||
|
||||
this._inProgressMigrationButton.count.value = inProgressMigrations.length.toString();
|
||||
let warningCount = 0;
|
||||
|
||||
const successfulMigration = this._migrationStatus.filter((value) => {
|
||||
for (let i = 0; i < inProgressMigrations.length; i++) {
|
||||
if (
|
||||
inProgressMigrations[i].asyncOperationResult?.error?.message ||
|
||||
inProgressMigrations[i].migrationContext.properties.migrationFailureError?.message ||
|
||||
inProgressMigrations[i].migrationContext.properties.migrationStatusDetails?.fileUploadBlockingErrors ||
|
||||
inProgressMigrations[i].migrationContext.properties.migrationStatusDetails?.restoreBlockingReason
|
||||
) {
|
||||
warningCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (warningCount > 0) {
|
||||
this._inProgressWarningMigrationButton.warningText!.value = loc.MIGRATION_INPROGRESS_WARNING(warningCount);
|
||||
this._inProgressMigrationButton.container.display = 'none';
|
||||
this._inProgressWarningMigrationButton.container.display = 'inline';
|
||||
} else {
|
||||
this._inProgressMigrationButton.container.display = 'inline';
|
||||
this._inProgressWarningMigrationButton.container.display = 'none';
|
||||
}
|
||||
this._inProgressMigrationButton.count.value = inProgressMigrations.length.toString();
|
||||
this._inProgressWarningMigrationButton.count.value = inProgressMigrations.length.toString();
|
||||
|
||||
const successfulMigration = migrationStatus.filter((value) => {
|
||||
const status = value.migrationContext.properties.migrationStatus;
|
||||
return status === 'Succeeded';
|
||||
});
|
||||
|
||||
this._successfulMigrationButton.count.value = successfulMigration.length.toString();
|
||||
|
||||
const currentConnection = (await azdata.connection.getCurrentConnection());
|
||||
const migrationDatabases = new Set(
|
||||
this._migrationStatus.map((value) => {
|
||||
migrationStatus.map((value) => {
|
||||
return value.migrationContext.properties.sourceDatabaseName;
|
||||
}));
|
||||
const serverDatabases = await azdata.connection.listDatabases(currentConnection.connectionId);
|
||||
@@ -260,26 +294,18 @@ export class DashboardWidget {
|
||||
private createStatusCard(
|
||||
cardIconPath: IconPath,
|
||||
cardTitle: string,
|
||||
cardDescription: string
|
||||
): StatusCard {
|
||||
|
||||
const cardTitleText = this._view.modelBuilder.text().withProps({ value: cardTitle }).withProps({
|
||||
CSSStyles: {
|
||||
'height': '40px',
|
||||
'height': '23px',
|
||||
'margin-top': '15px',
|
||||
'margin-bottom': '0px',
|
||||
'width': '300px',
|
||||
'font-size': '14px',
|
||||
}
|
||||
}).component();
|
||||
const cardDescriptionText = this._view.modelBuilder.text().withProps({ value: cardDescription }).withProps({
|
||||
CSSStyles: {
|
||||
'height': '0px',
|
||||
'margin-top': '0px',
|
||||
'margin-bottom': '0px',
|
||||
'width': '300px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const cardCount = this._view.modelBuilder.text().withProps({
|
||||
value: '0',
|
||||
CSSStyles: {
|
||||
@@ -289,22 +315,123 @@ export class DashboardWidget {
|
||||
}
|
||||
}).component();
|
||||
|
||||
const flex = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'width': '400px',
|
||||
'height': '50px',
|
||||
'margin-top': '10px',
|
||||
'border': '1px solid',
|
||||
}
|
||||
}).component();
|
||||
|
||||
const img = this._view.modelBuilder.image().withProps({
|
||||
iconPath: cardIconPath!.light,
|
||||
iconHeight: 24,
|
||||
iconWidth: 24,
|
||||
width: 64,
|
||||
height: 30,
|
||||
CSSStyles: {
|
||||
'margin-top': '10px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
flex.addItem(img, {
|
||||
flex: '0'
|
||||
});
|
||||
flex.addItem(cardTitleText, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'width': '300px'
|
||||
}
|
||||
});
|
||||
flex.addItem(cardCount, {
|
||||
flex: '0'
|
||||
});
|
||||
|
||||
const compositeButton = this._view.modelBuilder.divContainer().withItems([flex]).withProps({
|
||||
ariaRole: 'button',
|
||||
ariaLabel: 'show status',
|
||||
clickable: true
|
||||
}).component();
|
||||
return {
|
||||
container: compositeButton,
|
||||
count: cardCount
|
||||
};
|
||||
}
|
||||
|
||||
private createStatusWithSubtextCard(
|
||||
cardIconPath: IconPath,
|
||||
cardTitle: string,
|
||||
cardDescription: string
|
||||
): StatusCard {
|
||||
|
||||
const cardTitleText = this._view.modelBuilder.text().withProps({ value: cardTitle }).withProps({
|
||||
CSSStyles: {
|
||||
'height': '23px',
|
||||
'margin-top': '15px',
|
||||
'margin-bottom': '0px',
|
||||
'width': '300px',
|
||||
'font-size': '14px',
|
||||
}
|
||||
}).component();
|
||||
|
||||
const cardDescriptionWarning = this._view.modelBuilder.image().withProps({
|
||||
iconPath: IconPathHelper.warning,
|
||||
iconWidth: 12,
|
||||
iconHeight: 12,
|
||||
width: 12,
|
||||
height: 17
|
||||
}).component();
|
||||
|
||||
const cardDescriptionText = this._view.modelBuilder.text().withProps({ value: cardDescription }).withProps({
|
||||
CSSStyles: {
|
||||
'height': '13px',
|
||||
'margin-top': '0px',
|
||||
'margin-bottom': '0px',
|
||||
'width': '250px',
|
||||
'font-height': '13px',
|
||||
'margin': '0 0 0 4px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const subTextContainer = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'justify-content': 'left',
|
||||
}
|
||||
}).component();
|
||||
|
||||
subTextContainer.addItem(cardDescriptionWarning, {
|
||||
flex: '0 0 auto'
|
||||
});
|
||||
|
||||
subTextContainer.addItem(cardDescriptionText, {
|
||||
flex: '0 0 auto'
|
||||
});
|
||||
|
||||
const cardCount = this._view.modelBuilder.text().withProps({
|
||||
value: '0',
|
||||
CSSStyles: {
|
||||
'font-size': '28px',
|
||||
'line-height': '28px',
|
||||
'margin-top': '15px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().withItems([
|
||||
cardTitleText,
|
||||
cardDescriptionText
|
||||
subTextContainer
|
||||
]).withLayout({
|
||||
flexFlow: 'column'
|
||||
}).withProps({
|
||||
CSSStyles: {
|
||||
'width': '300px',
|
||||
'height': '50px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const flex = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'width': '400px',
|
||||
'height': '50px',
|
||||
'height': '70px',
|
||||
'margin-top': '10px',
|
||||
'border': '1px solid'
|
||||
}
|
||||
@@ -312,10 +439,13 @@ export class DashboardWidget {
|
||||
|
||||
const img = this._view.modelBuilder.image().withProps({
|
||||
iconPath: cardIconPath!.light,
|
||||
iconHeight: 16,
|
||||
iconWidth: 16,
|
||||
iconHeight: 24,
|
||||
iconWidth: 24,
|
||||
width: 64,
|
||||
height: 50
|
||||
height: 30,
|
||||
CSSStyles: {
|
||||
'margin-top': '20px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
flex.addItem(img, {
|
||||
@@ -338,7 +468,10 @@ export class DashboardWidget {
|
||||
}).component();
|
||||
return {
|
||||
container: compositeButton,
|
||||
count: cardCount
|
||||
count: cardCount,
|
||||
textContainer: flexContainer,
|
||||
warningContainer: subTextContainer,
|
||||
warningText: cardDescriptionText
|
||||
};
|
||||
}
|
||||
|
||||
@@ -377,7 +510,7 @@ export class DashboardWidget {
|
||||
const statusContainerTitle = view.modelBuilder.text().withProps({
|
||||
value: loc.DATABASE_MIGRATION_STATUS,
|
||||
CSSStyles: {
|
||||
'font-size': '18px',
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px',
|
||||
'width': '290px'
|
||||
@@ -393,7 +526,8 @@ export class DashboardWidget {
|
||||
}).component();
|
||||
|
||||
this._viewAllMigrationsButton.onDidClick(async (e) => {
|
||||
new MigrationStatusDialog(this._migrationStatus ? this._migrationStatus : await this.getMigrations(), MigrationCategory.ALL).initialize();
|
||||
const migrationStatus = await this.getCurrentMigrations();
|
||||
new MigrationStatusDialog(migrationStatus ? migrationStatus : await this.getMigrations(), MigrationCategory.ALL).initialize();
|
||||
});
|
||||
|
||||
const refreshButton = view.modelBuilder.hyperlink().withProps({
|
||||
@@ -444,24 +578,37 @@ export class DashboardWidget {
|
||||
|
||||
this._inProgressMigrationButton = this.createStatusCard(
|
||||
IconPathHelper.inProgressMigration,
|
||||
loc.MIGRATION_IN_PROGRESS,
|
||||
''
|
||||
loc.MIGRATION_IN_PROGRESS
|
||||
);
|
||||
this._inProgressMigrationButton.container.onDidClick((e) => {
|
||||
const dialog = new MigrationStatusDialog(this._migrationStatus, MigrationCategory.ONGOING);
|
||||
this._inProgressMigrationButton.container.onDidClick(async (e) => {
|
||||
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), MigrationCategory.ONGOING);
|
||||
dialog.initialize();
|
||||
});
|
||||
|
||||
this._migrationStatusCardsContainer.addItem(
|
||||
this._inProgressMigrationButton.container
|
||||
);
|
||||
|
||||
this._successfulMigrationButton = this.createStatusCard(
|
||||
IconPathHelper.completedMigration,
|
||||
loc.MIGRATION_COMPLETED,
|
||||
this._inProgressWarningMigrationButton = this.createStatusWithSubtextCard(
|
||||
IconPathHelper.inProgressMigration,
|
||||
loc.MIGRATION_IN_PROGRESS,
|
||||
''
|
||||
);
|
||||
this._successfulMigrationButton.container.onDidClick((e) => {
|
||||
const dialog = new MigrationStatusDialog(this._migrationStatus, MigrationCategory.SUCCEEDED);
|
||||
this._inProgressWarningMigrationButton.container.onDidClick(async (e) => {
|
||||
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), MigrationCategory.ONGOING);
|
||||
dialog.initialize();
|
||||
});
|
||||
|
||||
this._migrationStatusCardsContainer.addItem(
|
||||
this._inProgressWarningMigrationButton.container
|
||||
);
|
||||
|
||||
this._successfulMigrationButton = this.createStatusCard(
|
||||
IconPathHelper.completedMigration,
|
||||
loc.MIGRATION_COMPLETED
|
||||
);
|
||||
this._successfulMigrationButton.container.onDidClick(async (e) => {
|
||||
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), MigrationCategory.SUCCEEDED);
|
||||
dialog.initialize();
|
||||
});
|
||||
this._migrationStatusCardsContainer.addItem(
|
||||
@@ -470,8 +617,7 @@ export class DashboardWidget {
|
||||
|
||||
this._notStartedMigrationCard = this.createStatusCard(
|
||||
IconPathHelper.notStartedMigration,
|
||||
loc.MIGRATION_NOT_STARTED,
|
||||
loc.CHOOSE_TO_MIGRATE_TO_AZURE_SQL
|
||||
loc.MIGRATION_NOT_STARTED
|
||||
);
|
||||
this._notStartedMigrationCard.container.onDidClick((e) => {
|
||||
vscode.window.showInformationMessage('Feature coming soon');
|
||||
@@ -546,16 +692,6 @@ export class DashboardWidget {
|
||||
});
|
||||
|
||||
const videosContainer = this.createVideoLinkContainers(view, [
|
||||
{
|
||||
iconPath: IconPathHelper.sqlMiVideoThumbnail,
|
||||
description: loc.HELP_VIDEO1_TITLE,
|
||||
link: 'https://www.youtube.com/watch?v=sE99cSoFOHs' //TODO: Fix Video link
|
||||
},
|
||||
{
|
||||
iconPath: IconPathHelper.sqlVmVideoThumbnail,
|
||||
description: loc.HELP_VIDEO2_TITLE,
|
||||
link: 'https://www.youtube.com/watch?v=R4GCBoxADyQ' //TODO: Fix video link
|
||||
}
|
||||
]);
|
||||
const viewPanelStyle = {
|
||||
'padding': '10px 5px 10px 10px',
|
||||
|
||||
@@ -681,7 +681,7 @@ export class SqlDatabaseTree {
|
||||
this._assessmentTitle.value = this._selectedIssue.checkId;
|
||||
this._descriptionText.value = this._selectedIssue.description;
|
||||
this._moreInfo.url = this._selectedIssue.helpLink;
|
||||
this._moreInfo.label = this._selectedIssue.helpLink;
|
||||
this._moreInfo.label = this._selectedIssue.message;
|
||||
this._impactedObjects = this._selectedIssue.impactedObjects;
|
||||
this._recommendationText.value = this._selectedIssue.message; //TODO: Expose correct property for recommendation.
|
||||
this._impactedObjectsTable.dataValues = this._selectedIssue.impactedObjects.map((object) => {
|
||||
|
||||
@@ -218,7 +218,7 @@ export class CreateSqlMigrationServiceDialog {
|
||||
if (!location) {
|
||||
errors.push(constants.INVALID_REGION_ERROR);
|
||||
}
|
||||
if (!migrationServiceName || migrationServiceName.length === 0) {
|
||||
if (!migrationServiceName || migrationServiceName.length < 3 || migrationServiceName.length > 63 || !/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.test(migrationServiceName)) {
|
||||
errors.push(constants.INVALID_SERVICE_NAME_ERROR);
|
||||
}
|
||||
return errors.join(os.EOL);
|
||||
|
||||
@@ -27,6 +27,7 @@ export class MigrationCutoverDialog {
|
||||
private _serverName!: azdata.TextComponent;
|
||||
private _serverVersion!: azdata.TextComponent;
|
||||
private _sourceDatabase!: azdata.TextComponent;
|
||||
private _targetDatabase!: azdata.TextComponent;
|
||||
private _targetServer!: azdata.TextComponent;
|
||||
private _targetVersion!: azdata.TextComponent;
|
||||
private _migrationStatus!: azdata.TextComponent;
|
||||
@@ -78,9 +79,11 @@ export class MigrationCutoverDialog {
|
||||
}
|
||||
});
|
||||
|
||||
const targetDatabase = this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
||||
const targetServer = this.createInfoField(loc.TARGET_SERVER, '');
|
||||
const targetVersion = this.createInfoField(loc.TARGET_VERSION, '');
|
||||
|
||||
this._targetDatabase = targetDatabase.text;
|
||||
this._targetServer = targetServer.text;
|
||||
this._targetVersion = targetVersion.text;
|
||||
|
||||
@@ -88,6 +91,11 @@ export class MigrationCutoverDialog {
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
|
||||
flexTarget.addItem(targetDatabase.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
}
|
||||
});
|
||||
flexTarget.addItem(targetServer.flexContainer, {
|
||||
CSSStyles: {
|
||||
'width': '230px'
|
||||
@@ -198,7 +206,7 @@ export class MigrationCutoverDialog {
|
||||
{
|
||||
value: loc.ACTIVE_BACKUP_FILES,
|
||||
width: 280,
|
||||
type: azdata.ColumnType.text
|
||||
type: azdata.ColumnType.text,
|
||||
},
|
||||
{
|
||||
value: loc.TYPE,
|
||||
@@ -226,7 +234,7 @@ export class MigrationCutoverDialog {
|
||||
],
|
||||
data: [],
|
||||
width: '800px',
|
||||
height: '600px',
|
||||
height: '300px',
|
||||
}).component();
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
@@ -307,7 +315,7 @@ export class MigrationCutoverDialog {
|
||||
});
|
||||
|
||||
this._cancelButton = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.discard,
|
||||
iconPath: IconPathHelper.cancel,
|
||||
iconHeight: '16px',
|
||||
iconWidth: '16px',
|
||||
label: loc.CANCEL_MIGRATION,
|
||||
@@ -383,7 +391,10 @@ export class MigrationCutoverDialog {
|
||||
}).component();
|
||||
|
||||
header.addItem(this._refreshLoader, {
|
||||
flex: '0'
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'margin-top': '15px'
|
||||
}
|
||||
});
|
||||
|
||||
return header;
|
||||
@@ -397,19 +408,20 @@ export class MigrationCutoverDialog {
|
||||
this._cancelButton.enabled = false;
|
||||
await this._model.fetchStatus();
|
||||
const errors = [];
|
||||
errors.push(this._model.migrationOpStatus.error.message);
|
||||
errors.push(this._model.migrationOpStatus.error?.message);
|
||||
errors.push(this._model.migrationStatus.properties.migrationFailureError?.message);
|
||||
errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
|
||||
errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.restoreBlockingReason);
|
||||
this._dialogObject.message = {
|
||||
text: errors.filter(e => e !== undefined).join(EOL),
|
||||
level: this._model.migrationStatus.properties.migrationStatus === MigrationStatus.InProgress ? azdata.window.MessageLevel.Warning : azdata.window.MessageLevel.Error
|
||||
level: (this._model.migrationStatus.properties.migrationStatus === MigrationStatus.InProgress || this._model.migrationStatus.properties.migrationStatus === 'Completing') ? azdata.window.MessageLevel.Warning : azdata.window.MessageLevel.Error
|
||||
};
|
||||
const sqlServerInfo = await azdata.connection.getServerInfo((await azdata.connection.getCurrentConnection()).connectionId);
|
||||
const sqlServerName = this._model._migration.sourceConnectionProfile.serverName;
|
||||
const sourceDatabaseName = this._model._migration.migrationContext.properties.sourceDatabaseName;
|
||||
const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!);
|
||||
const sqlServerVersion = versionName ? versionName : sqlServerInfo.serverVersion;
|
||||
const targetDatabaseName = this._model._migration.migrationContext.name;
|
||||
const targetServerName = this._model._migration.targetManagedInstance.name;
|
||||
let targetServerVersion;
|
||||
if (this._model.migrationStatus.id.includes('managedInstances')) {
|
||||
@@ -452,6 +464,7 @@ export class MigrationCutoverDialog {
|
||||
this._serverVersion.value = `${sqlServerVersion}
|
||||
${sqlServerInfo.serverVersion}`;
|
||||
|
||||
this._targetDatabase.value = targetDatabaseName;
|
||||
this._targetServer.value = targetServerName;
|
||||
this._targetVersion.value = targetServerVersion;
|
||||
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
import { MigrationContext, MigrationLocalStorage } from '../../models/migrationLocalStorage';
|
||||
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
|
||||
import { MigrationCategory, MigrationStatusDialogModel } from './migrationStatusDialogModel';
|
||||
import * as loc from '../../constants/strings';
|
||||
import { getDatabaseMigration } from '../../api/azure';
|
||||
export class MigrationStatusDialog {
|
||||
private _model: MigrationStatusDialogModel;
|
||||
private _dialogObject!: azdata.window.Dialog;
|
||||
@@ -81,7 +80,7 @@ export class MigrationStatusDialog {
|
||||
dark: IconPathHelper.refresh.dark
|
||||
},
|
||||
iconHeight: '16px',
|
||||
iconWidth: '16px',
|
||||
iconWidth: '20px',
|
||||
height: '30px',
|
||||
label: 'Refresh',
|
||||
}).component();
|
||||
@@ -90,7 +89,11 @@ export class MigrationStatusDialog {
|
||||
this.refreshTable();
|
||||
});
|
||||
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().component();
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'justify-content': 'left'
|
||||
}
|
||||
}).component();
|
||||
|
||||
flexContainer.addItem(this._searchBox, {
|
||||
flex: '0'
|
||||
@@ -109,7 +112,10 @@ export class MigrationStatusDialog {
|
||||
}).component();
|
||||
|
||||
flexContainer.addItem(this._refreshLoader, {
|
||||
flex: '0'
|
||||
flex: '0 0 auto',
|
||||
CSSStyles: {
|
||||
'margin-left': '20px'
|
||||
}
|
||||
});
|
||||
|
||||
return flexContainer;
|
||||
@@ -128,7 +134,7 @@ export class MigrationStatusDialog {
|
||||
return new Date(m1.migrationContext.properties.startedOn) > new Date(m2.migrationContext.properties.startedOn) ? -1 : 1;
|
||||
});
|
||||
|
||||
migrations.forEach((migration) => {
|
||||
migrations.forEach((migration, index) => {
|
||||
const migrationRow: azdata.DeclarativeTableCellValue[] = [];
|
||||
|
||||
const databaseHyperLink = this._view.modelBuilder.hyperlink().withProps({
|
||||
@@ -142,10 +148,6 @@ export class MigrationStatusDialog {
|
||||
value: databaseHyperLink,
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: migration.migrationContext.properties.migrationStatus ? migration.migrationContext.properties.migrationStatus : migration.migrationContext.properties.provisioningState
|
||||
});
|
||||
|
||||
const targetMigrationIcon = this._view.modelBuilder.image().withProps({
|
||||
iconPath: (migration.targetManagedInstance.type === 'microsoft.sql/managedinstances') ? IconPathHelper.sqlMiLogo : IconPathHelper.sqlVmLogo,
|
||||
iconWidth: '16px',
|
||||
@@ -163,7 +165,7 @@ export class MigrationStatusDialog {
|
||||
|
||||
const sqlMigrationContainer = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'justify-content': 'center'
|
||||
'justify-content': 'left'
|
||||
}
|
||||
}).component();
|
||||
sqlMigrationContainer.addItem(targetMigrationIcon, {
|
||||
@@ -186,6 +188,27 @@ export class MigrationStatusDialog {
|
||||
value: loc.ONLINE
|
||||
});
|
||||
|
||||
let migrationStatus = migration.migrationContext.properties.migrationStatus ? migration.migrationContext.properties.migrationStatus : migration.migrationContext.properties.provisioningState;
|
||||
|
||||
let warningCount = 0;
|
||||
|
||||
if (migration.asyncOperationResult?.error?.message) {
|
||||
warningCount++;
|
||||
}
|
||||
if (migration.migrationContext.properties.migrationFailureError?.message) {
|
||||
warningCount++;
|
||||
}
|
||||
if (migration.migrationContext.properties.migrationStatusDetails?.fileUploadBlockingErrors) {
|
||||
warningCount += migration.migrationContext.properties.migrationStatusDetails?.fileUploadBlockingErrors.length;
|
||||
}
|
||||
if (migration.migrationContext.properties.migrationStatusDetails?.restoreBlockingReason) {
|
||||
warningCount++;
|
||||
}
|
||||
|
||||
migrationRow.push({
|
||||
value: loc.STATUS_WARNING_COUNT(migrationStatus, warningCount)
|
||||
});
|
||||
|
||||
migrationRow.push({
|
||||
value: (migration.migrationContext.properties.startedOn) ? new Date(migration.migrationContext.properties.startedOn).toLocaleString() : '---'
|
||||
});
|
||||
@@ -202,21 +225,28 @@ export class MigrationStatusDialog {
|
||||
}
|
||||
}
|
||||
|
||||
private refreshTable(): void {
|
||||
private async refreshTable(): Promise<void> {
|
||||
this._refreshLoader.loading = true;
|
||||
this._model._migrations.forEach(async (migration) => {
|
||||
migration.migrationContext = await getDatabaseMigration(
|
||||
migration.azureAccount,
|
||||
migration.subscription,
|
||||
migration.targetManagedInstance.location,
|
||||
migration.migrationContext.id
|
||||
);
|
||||
});
|
||||
const currentConnection = await azdata.connection.getCurrentConnection();
|
||||
this._model._migrations = await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true);
|
||||
this.populateMigrationTable();
|
||||
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'
|
||||
};
|
||||
|
||||
this._statusTable = this._view.modelBuilder.declarativeTable().withProps({
|
||||
columns: [
|
||||
{
|
||||
@@ -224,54 +254,48 @@ export class MigrationStatusDialog {
|
||||
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'
|
||||
}
|
||||
rowCssStyles: rowCssStyle,
|
||||
headerCssStyles: headerCssStyles
|
||||
},
|
||||
{
|
||||
displayName: loc.TARGET_AZURE_SQL_INSTANCE_NAME,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
width: '300px',
|
||||
width: '170px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
rowCssStyles: rowCssStyle,
|
||||
headerCssStyles: headerCssStyles
|
||||
},
|
||||
{
|
||||
displayName: loc.MIGRATION_MODE,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '100px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
rowCssStyles: rowCssStyle,
|
||||
headerCssStyles: headerCssStyles
|
||||
},
|
||||
{
|
||||
displayName: loc.MIGRATION_STATUS,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: rowCssStyle,
|
||||
headerCssStyles: headerCssStyles
|
||||
},
|
||||
{
|
||||
displayName: loc.START_TIME,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
width: '120px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
rowCssStyles: rowCssStyle,
|
||||
headerCssStyles: headerCssStyles
|
||||
},
|
||||
{
|
||||
displayName: loc.FINISH_TIME,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
width: '120px',
|
||||
isReadOnly: true,
|
||||
rowCssStyles: {
|
||||
'text-align': 'center'
|
||||
}
|
||||
rowCssStyles: rowCssStyle,
|
||||
headerCssStyles: headerCssStyles
|
||||
}
|
||||
]
|
||||
}).component();
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as azdata from 'azdata';
|
||||
import { MigrationContext } from '../../models/migrationLocalStorage';
|
||||
|
||||
export class MigrationStatusDialogModel {
|
||||
|
||||
public statusDropdownValues: azdata.CategoryValue[] = [
|
||||
{
|
||||
displayName: 'Status: All',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { DatabaseMigration, SqlMigrationService, SqlManagedInstance, getDatabaseMigration } from '../api/azure';
|
||||
import { DatabaseMigration, SqlMigrationService, SqlManagedInstance, getMigrationStatus, AzureAsyncOperationResource, getMigrationAsyncOperationDetails } from '../api/azure';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
|
||||
@@ -27,12 +27,18 @@ export class MigrationLocalStorage {
|
||||
if (migration.sourceConnectionProfile.serverName === connectionProfile.serverName) {
|
||||
if (refreshStatus) {
|
||||
try {
|
||||
migration.migrationContext = await getDatabaseMigration(
|
||||
migration.migrationContext = await getMigrationStatus(
|
||||
migration.azureAccount,
|
||||
migration.subscription,
|
||||
migration.targetManagedInstance.location,
|
||||
migration.migrationContext.id
|
||||
migration.migrationContext
|
||||
);
|
||||
if (migration.asyncUrl) {
|
||||
migration.asyncOperationResult = await getMigrationAsyncOperationDetails(
|
||||
migration.azureAccount,
|
||||
migration.subscription,
|
||||
migration.asyncUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// Keeping only valid migrations in cache. Clearing all the migrations which return ResourceDoesNotExit error.
|
||||
@@ -89,5 +95,6 @@ export interface MigrationContext {
|
||||
azureAccount: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
controller: SqlMigrationService,
|
||||
asyncUrl: string
|
||||
asyncUrl: string,
|
||||
asyncOperationResult?: AzureAsyncOperationResource
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export enum MigrationSourceAuthenticationType {
|
||||
Sql = 'SqlAuthentication'
|
||||
}
|
||||
|
||||
export enum MigrationCutover {
|
||||
export enum MigrationMode {
|
||||
ONLINE,
|
||||
OFFLINE
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export interface NetworkShare {
|
||||
}
|
||||
|
||||
export interface DatabaseBackupModel {
|
||||
migrationCutover: MigrationCutover;
|
||||
migrationMode: MigrationMode;
|
||||
networkContainerType: NetworkContainerType;
|
||||
networkShareLocation: string;
|
||||
windowsUser: string;
|
||||
|
||||
@@ -103,6 +103,7 @@ export class AccountsSelectionPage extends MigrationWizardPage {
|
||||
this.wizard.message = {
|
||||
text: ''
|
||||
};
|
||||
this._azureAccountsDropdown.validate();
|
||||
});
|
||||
|
||||
const flexContainer = view.modelBuilder.flexContainer()
|
||||
|
||||
@@ -271,7 +271,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
}).withValidation((component) => {
|
||||
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
|
||||
if (component.value) {
|
||||
if (!/(?<=\\\\)[^\\]*/.test(component.value)) {
|
||||
if (!/^[\\\/]{2,}[^\\\/]+[\\\/]+[^\\\/]+/.test(component.value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
.withValidation((component) => {
|
||||
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
|
||||
if (component.value) {
|
||||
if (!/(?<=\\).*$/.test(component.value)) {
|
||||
if (!/^[A-Za-z0-9\\\._-]{7,}$/.test(component.value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -512,10 +512,14 @@ export class DatabaseBackupPage extends MigrationWizardPage {
|
||||
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(this.migrationStateModel._targetServerInstance.name);
|
||||
return false;
|
||||
}
|
||||
if (c.value!.length < 1 || c.value!.length > 128 || !/[^<>*%&:\\\/?]/.test(c.value!)) {
|
||||
c.validationErrorMessage = constants.INVALID_TARGET_NAME_ERROR;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).component();
|
||||
targetNameNetworkInputBox.onTextChanged((value) => {
|
||||
this.migrationStateModel._targetDatabaseNames[index] = value;
|
||||
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
|
||||
});
|
||||
this._targetDatabaseNames.push(targetNameNetworkInputBox);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationCutover, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import { MigrationMode, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../constants/strings';
|
||||
|
||||
export class MigrationModePage extends MigrationWizardPage {
|
||||
@@ -57,11 +57,11 @@ export class MigrationModePage extends MigrationWizardPage {
|
||||
}
|
||||
}).component();
|
||||
|
||||
this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE;
|
||||
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
|
||||
|
||||
onlineButton.onDidChangeCheckedState((e) => {
|
||||
if (e) {
|
||||
this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE;
|
||||
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
|
||||
private _view!: azdata.ModelView;
|
||||
private _igComponent!: azdata.TextComponent;
|
||||
private _assessmentStatusIcon!: azdata.ImageComponent;
|
||||
private _detailsComponent!: azdata.TextComponent;
|
||||
private _chooseTargetComponent!: azdata.DivContainer;
|
||||
private _azureSubscriptionText!: azdata.TextComponent;
|
||||
@@ -63,7 +64,31 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
protected async registerContent(view: azdata.ModelView) {
|
||||
this._view = view;
|
||||
this._igComponent = this.createStatusComponent(view); // The first component giving basic information
|
||||
this._assessmentStatusIcon = this._view.modelBuilder.image().withProps({
|
||||
iconPath: IconPathHelper.completedMigration,
|
||||
iconHeight: 17,
|
||||
iconWidth: 17,
|
||||
width: 17,
|
||||
height: 20
|
||||
}).component();
|
||||
const igContainer = this._view.modelBuilder.flexContainer().component();
|
||||
igContainer.addItem(this._assessmentStatusIcon, {
|
||||
flex: '0 0 auto'
|
||||
});
|
||||
igContainer.addItem(this._igComponent, {
|
||||
flex: '0 0 auto'
|
||||
});
|
||||
|
||||
this._detailsComponent = this.createDetailsComponent(view); // The details of what can be moved
|
||||
|
||||
const statusContainer = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).withItems(
|
||||
[
|
||||
igContainer,
|
||||
this._detailsComponent
|
||||
]
|
||||
).component();
|
||||
this._chooseTargetComponent = await this.createChooseTargetComponent(view);
|
||||
this._azureSubscriptionText = this.createAzureSubscriptionText(view);
|
||||
|
||||
@@ -164,11 +189,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
[
|
||||
{
|
||||
title: '',
|
||||
component: this._igComponent
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
component: this._detailsComponent
|
||||
component: statusContainer
|
||||
},
|
||||
{
|
||||
title: constants.SKU_RECOMMENDATION_CHOOSE_A_TARGET,
|
||||
@@ -212,14 +233,20 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
private createStatusComponent(view: azdata.ModelView): azdata.TextComponent {
|
||||
const component = view.modelBuilder.text().withProps({
|
||||
CSSStyles: {
|
||||
'font-size': '14px'
|
||||
'font-size': '14px',
|
||||
'margin': '0 0 0 8px',
|
||||
'line-height': '20px'
|
||||
}
|
||||
}).component();
|
||||
return component;
|
||||
}
|
||||
|
||||
private createDetailsComponent(view: azdata.ModelView): azdata.TextComponent {
|
||||
const component = view.modelBuilder.text().component();
|
||||
const component = view.modelBuilder.text().withProps({
|
||||
CSSStyles: {
|
||||
'font-size': '13px'
|
||||
}
|
||||
}).component();
|
||||
return component;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||
import { MigrationMode, MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../constants/strings';
|
||||
import { createHeadingTextComponent, createInformationRow } from './wizardController';
|
||||
|
||||
@@ -36,19 +36,32 @@ export class SummaryPage extends MigrationWizardPage {
|
||||
public async onPageEnter(): Promise<void> {
|
||||
this._flexContainer.addItems(
|
||||
[
|
||||
createHeadingTextComponent(this._view, constants.AZURE_ACCOUNT_LINKED),
|
||||
createHeadingTextComponent(this._view, this.migrationStateModel._azureAccount.displayInfo.displayName),
|
||||
createHeadingTextComponent(this._view, constants.MIGRATION_TARGET),
|
||||
createInformationRow(this._view, constants.TYPE, (this.migrationStateModel._targetServerInstance.type === 'microsoft.compute/virtualmachines') ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
|
||||
createInformationRow(this._view, constants.SUMMARY_MI_TYPE, this.migrationStateModel._targetServerInstance.name),
|
||||
createInformationRow(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL, this.migrationStateModel._migrationDbs.length.toString()),
|
||||
createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE),
|
||||
this.createNetworkContainerRows(),
|
||||
createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE),
|
||||
createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._sqlMigrationService?.name!),
|
||||
createInformationRow(this._view, constants.SUMMARY_IR_NODE, this.migrationStateModel._nodeNames.join(', ')),
|
||||
createHeadingTextComponent(this._view, constants.ACCOUNTS_SELECTION_PAGE_TITLE),
|
||||
createInformationRow(this._view, constants.ACCOUNTS_SELECTION_PAGE_TITLE, this.migrationStateModel._azureAccount.displayInfo.displayName),
|
||||
|
||||
createHeadingTextComponent(this._view, constants.SOURCE_DATABASES),
|
||||
createInformationRow(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL, this.migrationStateModel._migrationDbs.length.toString()),
|
||||
|
||||
createHeadingTextComponent(this._view, constants.SKU_RECOMMENDATION_PAGE_TITLE),
|
||||
createInformationRow(this._view, constants.SKU_RECOMMENDATION_PAGE_TITLE, (this.migrationStateModel._targetServerInstance.type === 'microsoft.compute/virtualmachines') ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
|
||||
createInformationRow(this._view, constants.LOCATION, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.location)),
|
||||
createInformationRow(this._view, constants.RESOURCE_GROUP, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.resourceGroup!)),
|
||||
createInformationRow(this._view, (this.migrationStateModel._targetServerInstance.type === 'microsoft.compute/virtualmachines') ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.name!)),
|
||||
|
||||
createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL),
|
||||
createInformationRow(this._view, constants.MODE, this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE ? constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL : constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL),
|
||||
|
||||
createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE),
|
||||
await this.createNetworkContainerRows(),
|
||||
|
||||
createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
|
||||
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._sqlMigrationService.location),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._sqlMigrationService.properties.resourceGroup),
|
||||
createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._targetSubscription.name),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._sqlMigrationService.name),
|
||||
createInformationRow(this._view, constants.SHIR, this.migrationStateModel._nodeNames[0]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -63,7 +76,7 @@ export class SummaryPage extends MigrationWizardPage {
|
||||
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
||||
}
|
||||
|
||||
private createNetworkContainerRows(): azdata.FlexContainer {
|
||||
private async createNetworkContainerRows(): Promise<azdata.FlexContainer> {
|
||||
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
@@ -71,11 +84,14 @@ export class SummaryPage extends MigrationWizardPage {
|
||||
case NetworkContainerType.NETWORK_SHARE:
|
||||
flexContainer.addItems(
|
||||
[
|
||||
createInformationRow(this._view, constants.TYPE, constants.NETWORK_SHARE),
|
||||
createInformationRow(this._view, constants.DATABASE_BACKUP_NETWORK_SHARE_LOCATION_LABEL, this.migrationStateModel._databaseBackup.networkShareLocation),
|
||||
createInformationRow(this._view, constants.BACKUP_LOCATION, constants.NETWORK_SHARE),
|
||||
createInformationRow(this._view, constants.NETWORK_SHARE, this.migrationStateModel._databaseBackup.networkShareLocation),
|
||||
createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.windowsUser),
|
||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
|
||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name),
|
||||
createHeadingTextComponent(this._view, constants.AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
|
||||
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.storageAccount.location),
|
||||
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.storageAccount.resourceGroup!),
|
||||
createInformationRow(this._view, constants.STORAGE_ACCOUNT, this.migrationStateModel._databaseBackup.storageAccount.name!),
|
||||
createHeadingTextComponent(this._view, 'Target Databases:')
|
||||
]
|
||||
);
|
||||
|
||||
@@ -110,7 +110,7 @@ export function createHeadingTextComponent(view: azdata.ModelView, value: string
|
||||
const component = createTextCompononent(view, value);
|
||||
component.updateCssStyles({
|
||||
'font-size': '13px',
|
||||
'font-weight': 'bold'
|
||||
'font-weight': 'bold',
|
||||
});
|
||||
return component;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user