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