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:
Aasim Khan
2021-04-02 18:49:34 -07:00
committed by GitHub
parent fde5caa9a4
commit 684dfc9760
19 changed files with 433 additions and 151 deletions

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0C8.71058 0.0013355 9.41747 0.102319 10.1 0.3C10.7724 0.46224 11.4141 0.732411 12 1.1C12.6054 1.44216 13.1749 1.84417 13.7 2.3C14.1558 2.82512 14.5578 3.39464 14.9 4C15.2676 4.58594 15.5378 5.2276 15.7 5.9C16.1 7.27143 16.1 8.72857 15.7 10.1C15.5378 10.7724 15.2676 11.4141 14.9 12C14.5578 12.6054 14.1558 13.1749 13.7 13.7C13.1749 14.1558 12.6054 14.5578 12 14.9C11.4141 15.2676 10.7724 15.5378 10.1 15.7C8.72857 16.1 7.27143 16.1 5.9 15.7C5.2276 15.5378 4.58594 15.2676 4 14.9C3.39464 14.5578 2.82512 14.1558 2.3 13.7C1.84417 13.1749 1.44216 12.6054 1.1 12C0.732411 11.4141 0.46224 10.7724 0.3 10.1C-0.1 8.72857 -0.1 7.27143 0.3 5.9C0.46224 5.2276 0.732411 4.58594 1.1 4C1.44216 3.39464 1.84417 2.82512 2.3 2.3C2.82512 1.84417 3.39464 1.44216 4 1.1C4.58594 0.732411 5.2276 0.46224 5.9 0.3C6.58253 0.102319 7.28942 0.0013355 8 0V0ZM8 15C8.81874 15.0292 9.63498 14.8931 10.4 14.6C11.1869 14.2645 11.9265 13.8275 12.6 13.3L2.7 3.4C2.1725 4.07345 1.73546 4.81307 1.4 5.6C1.10685 6.36502 0.970814 7.18126 1 8C1.00215 8.64496 1.10333 9.28576 1.3 9.9C1.42902 10.4731 1.66664 11.0163 2 11.5C2.25062 12.0472 2.62764 12.527 3.1 12.9C3.47297 13.3724 3.95281 13.7494 4.5 14L6.1 14.8L8 15ZM13.3 12.6C13.8275 11.9265 14.2645 11.1869 14.6 10.4C14.8931 9.63498 15.0292 8.81874 15 8C14.9827 7.36244 14.9158 6.72719 14.8 6.1C14.594 5.53844 14.3256 5.00175 14 4.5C13.7494 3.95281 13.3724 3.47297 12.9 3.1C12.527 2.62764 12.0472 2.25062 11.5 2C11.0163 1.66664 10.4731 1.42902 9.9 1.3C9.28576 1.10333 8.64496 1.00215 8 1C7.18126 0.970814 6.36502 1.10685 5.6 1.4C4.81307 1.73546 4.07345 2.1725 3.4 2.7L13.3 12.6Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8438 10.44L6.94704 0.57825C6.54804 -0.19275 5.45079 -0.19275 5.05404 0.57825L0.156544 10.287C-0.242456 11.058 0.156544 11.9835 0.953794 11.9835H11.0465C11.8438 12.1365 12.242 11.2125 11.8438 10.44Z" fill="#DB7500"/>
<path d="M5.99984 10.8368C6.60003 10.8368 7.08659 10.3502 7.08659 9.75002C7.08659 9.14982 6.60003 8.66327 5.99984 8.66327C5.39964 8.66327 4.91309 9.14982 4.91309 9.75002C4.91309 10.3502 5.39964 10.8368 5.99984 10.8368Z" fill="white"/>
<path d="M4.9502 3L5.3252 7.5H6.6752L7.0502 3H4.9502Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -2,7 +2,7 @@
"name": "sql-migration",
"displayName": "%displayName%",
"description": "%description%",
"version": "0.0.7",
"version": "0.0.8",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",

View File

@@ -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')
};
}
}

View File

@@ -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");

View File

@@ -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',

View File

@@ -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) => {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -7,7 +7,6 @@ import * as azdata from 'azdata';
import { MigrationContext } from '../../models/migrationLocalStorage';
export class MigrationStatusDialogModel {
public statusDropdownValues: azdata.CategoryValue[] = [
{
displayName: 'Status: All',

View File

@@ -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
}

View File

@@ -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;

View File

@@ -103,6 +103,7 @@ export class AccountsSelectionPage extends MigrationWizardPage {
this.wizard.message = {
text: ''
};
this._azureAccountsDropdown.validate();
});
const flexContainer = view.modelBuilder.flexContainer()

View File

@@ -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);

View File

@@ -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;
}
});

View File

@@ -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;
}

View File

@@ -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:')
]
);

View File

@@ -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;
}