mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 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:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user