diff --git a/extensions/sql-migration/images/cancel.svg b/extensions/sql-migration/images/cancel.svg
new file mode 100644
index 0000000000..3978d78eaa
--- /dev/null
+++ b/extensions/sql-migration/images/cancel.svg
@@ -0,0 +1,3 @@
+
diff --git a/extensions/sql-migration/images/warning.svg b/extensions/sql-migration/images/warning.svg
new file mode 100644
index 0000000000..5baa4db4e3
--- /dev/null
+++ b/extensions/sql-migration/images/warning.svg
@@ -0,0 +1,5 @@
+
diff --git a/extensions/sql-migration/package.json b/extensions/sql-migration/package.json
index 821b8dc64d..3b21dfdf42 100644
--- a/extensions/sql-migration/package.json
+++ b/extensions/sql-migration/package.json
@@ -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",
diff --git a/extensions/sql-migration/src/constants/iconPathHelper.ts b/extensions/sql-migration/src/constants/iconPathHelper.ts
index 2dfb61f865..338ee60f11 100644
--- a/extensions/sql-migration/src/constants/iconPathHelper.ts
+++ b/extensions/sql-migration/src/constants/iconPathHelper.ts
@@ -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')
+ };
}
}
diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts
index 85d6a4ba2d..cdd16aba2e 100644
--- a/extensions/sql-migration/src/constants/strings.ts
+++ b/extensions/sql-migration/src/constants/strings.ts
@@ -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");
diff --git a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
index 4ee1d1e4fb..009dea4cd6 100644
--- a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
+++ b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
@@ -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 = new Map();
private _viewAllMigrationsButton!: azdata.ButtonComponent;
constructor() {
@@ -46,6 +49,16 @@ export class DashboardWidget {
});
}
+ private async getCurrentMigrations(): Promise {
+ const connectionId = (await azdata.connection.getCurrentConnection()).connectionId;
+ return this._migrationStatusMap.get(connectionId)!;
+ }
+
+ private async setCurrentMigrations(migrations: MigrationContext[]): Promise {
+ 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',
diff --git a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts
index 93283b1a07..a7b61e7c04 100644
--- a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts
+++ b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts
@@ -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) => {
diff --git a/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts b/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts
index f3f4d66c48..1bb3369219 100644
--- a/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts
+++ b/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts
@@ -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);
diff --git a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts
index 279ac99987..3432021726 100644
--- a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts
+++ b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts
@@ -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;
diff --git a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts
index 641ad15e25..9b5906a547 100644
--- a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts
+++ b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts
@@ -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 {
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();
diff --git a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialogModel.ts b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialogModel.ts
index 30ae26f14f..6b6c9d59af 100644
--- a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialogModel.ts
+++ b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialogModel.ts
@@ -7,7 +7,6 @@ import * as azdata from 'azdata';
import { MigrationContext } from '../../models/migrationLocalStorage';
export class MigrationStatusDialogModel {
-
public statusDropdownValues: azdata.CategoryValue[] = [
{
displayName: 'Status: All',
diff --git a/extensions/sql-migration/src/models/migrationLocalStorage.ts b/extensions/sql-migration/src/models/migrationLocalStorage.ts
index 3d8f201a47..4c5dfe8b96 100644
--- a/extensions/sql-migration/src/models/migrationLocalStorage.ts
+++ b/extensions/sql-migration/src/models/migrationLocalStorage.ts
@@ -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
}
diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts
index d0f73b527c..0074ba1c33 100644
--- a/extensions/sql-migration/src/models/stateMachine.ts
+++ b/extensions/sql-migration/src/models/stateMachine.ts
@@ -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;
diff --git a/extensions/sql-migration/src/wizard/accountsSelectionPage.ts b/extensions/sql-migration/src/wizard/accountsSelectionPage.ts
index a0402f75e5..df693b4a5d 100644
--- a/extensions/sql-migration/src/wizard/accountsSelectionPage.ts
+++ b/extensions/sql-migration/src/wizard/accountsSelectionPage.ts
@@ -103,6 +103,7 @@ export class AccountsSelectionPage extends MigrationWizardPage {
this.wizard.message = {
text: ''
};
+ this._azureAccountsDropdown.validate();
});
const flexContainer = view.modelBuilder.flexContainer()
diff --git a/extensions/sql-migration/src/wizard/databaseBackupPage.ts b/extensions/sql-migration/src/wizard/databaseBackupPage.ts
index 82dfc704e8..197282ef85 100644
--- a/extensions/sql-migration/src/wizard/databaseBackupPage.ts
+++ b/extensions/sql-migration/src/wizard/databaseBackupPage.ts
@@ -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);
diff --git a/extensions/sql-migration/src/wizard/migrationModePage.ts b/extensions/sql-migration/src/wizard/migrationModePage.ts
index 119f8d8e57..d9157708ec 100644
--- a/extensions/sql-migration/src/wizard/migrationModePage.ts
+++ b/extensions/sql-migration/src/wizard/migrationModePage.ts
@@ -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;
}
});
diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
index 64e50e7e71..8f425a1a86 100644
--- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
+++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
@@ -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;
}
diff --git a/extensions/sql-migration/src/wizard/summaryPage.ts b/extensions/sql-migration/src/wizard/summaryPage.ts
index 07072800fc..408d61a882 100644
--- a/extensions/sql-migration/src/wizard/summaryPage.ts
+++ b/extensions/sql-migration/src/wizard/summaryPage.ts
@@ -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 {
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 {
}
- private createNetworkContainerRows(): azdata.FlexContainer {
+ private async createNetworkContainerRows(): Promise {
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:')
]
);
diff --git a/extensions/sql-migration/src/wizard/wizardController.ts b/extensions/sql-migration/src/wizard/wizardController.ts
index 6868fffe8b..514e50f439 100644
--- a/extensions/sql-migration/src/wizard/wizardController.ts
+++ b/extensions/sql-migration/src/wizard/wizardController.ts
@@ -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;
}