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

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