Retry sql migration (#17376)

This commit is contained in:
Rachel Kim
2021-10-21 10:06:10 -07:00
committed by GitHub
parent decad711c5
commit 4b26be5742
23 changed files with 663 additions and 337 deletions

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 1.00029H14V5.00027H10V4.00027H12.1953C11.9297 3.54194 11.612 3.12788 11.2422 2.75809C10.8724 2.3883 10.4609 2.0732 10.0078 1.81278C9.55469 1.55237 9.07552 1.35185 8.57031 1.21122C8.0651 1.0706 7.54167 1.00029 7 1.00029C6.28125 1.00029 5.59635 1.12008 4.94531 1.35966C4.29427 1.59924 3.70833 1.93518 3.1875 2.36747C2.66667 2.79976 2.22396 3.31278 1.85938 3.90652C1.49479 4.50027 1.24479 5.14871 1.10938 5.85183L0.125 5.65652C0.229167 5.10964 0.393229 4.59142 0.617188 4.10183C0.841146 3.61225 1.11719 3.15913 1.44531 2.74247C1.77344 2.3258 2.14062 1.94559 2.54688 1.60184C2.95312 1.2581 3.39583 0.971639 3.875 0.742474C4.35417 0.513308 4.85417 0.331017 5.375 0.195601C5.89583 0.0601849 6.4375 -0.00491896 7 0.000289351C7.61458 0.000289351 8.21354 0.078414 8.79688 0.234663C9.38021 0.390913 9.92969 0.617474 10.4453 0.914348C10.9609 1.21122 11.4375 1.56799 11.875 1.98466C12.3125 2.40132 12.6875 2.87007 13 3.3909V1.00029ZM7 13.0002C7.71875 13.0002 8.40365 12.8804 9.05469 12.6409C9.70573 12.4013 10.2917 12.0653 10.8125 11.6331C11.3333 11.2008 11.776 10.6903 12.1406 10.1018C12.5052 9.51327 12.7552 8.86483 12.8906 8.1565L13.8672 8.344C13.7109 9.16692 13.4219 9.92212 13 10.6096C12.5781 11.2971 12.0625 11.8935 11.4531 12.3987C10.8438 12.9039 10.1589 13.2971 9.39844 13.5784C8.63802 13.8596 7.83854 14.0002 7 14.0002C6.38021 14.0002 5.77865 13.9221 5.19531 13.7659C4.61198 13.6096 4.0599 13.383 3.53906 13.0862C3.01823 12.7893 2.54427 12.4351 2.11719 12.0237C1.6901 11.6122 1.31771 11.1409 1 10.6096V13.0002H0V9.00025H4V10.0002H1.80469C2.07031 10.4638 2.38802 10.8805 2.75781 11.2502C3.1276 11.62 3.53906 11.9325 3.99219 12.1877C4.44531 12.4429 4.92188 12.6435 5.42188 12.7893C5.92188 12.9351 6.44792 13.0054 7 13.0002Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -2,7 +2,7 @@
"name": "sql-migration", "name": "sql-migration",
"displayName": "%displayName%", "displayName": "%displayName%",
"description": "%description%", "description": "%description%",
"version": "0.1.8", "version": "0.1.9",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
@@ -81,6 +81,11 @@
"command": "sqlmigration.cancel.migration", "command": "sqlmigration.cancel.migration",
"title": "%cancel-migration-menu%", "title": "%cancel-migration-menu%",
"category": "%migration-context-menu-category%" "category": "%migration-context-menu-category%"
},
{
"command": "sqlmigration.retry.migration",
"title": "%retry-migration-menu%",
"category": "%migration-context-menu-category%"
} }
], ],
"menu": { "menu": {
@@ -116,6 +121,10 @@
{ {
"command": "sqlmigration.cancel.migration", "command": "sqlmigration.cancel.migration",
"when": "false" "when": "false"
},
{
"command": "sqlmigration.retry.migration",
"when": "false"
} }
] ]
}, },
@@ -174,4 +183,4 @@
"devDependencies": { "devDependencies": {
"@types/uuid": "^8.3.1" "@types/uuid": "^8.3.1"
} }
} }

View File

@@ -14,5 +14,6 @@
"view-target-menu": "Azure SQL Target details", "view-target-menu": "Azure SQL Target details",
"view-service-menu": "Database Migration Service details", "view-service-menu": "Database Migration Service details",
"copy-migration-menu": "Copy migration details", "copy-migration-menu": "Copy migration details",
"cancel-migration-menu": "Cancel migration" "cancel-migration-menu": "Cancel migration",
"retry-migration-menu": "Retry migration"
} }

View File

@@ -362,6 +362,15 @@ export function getFullResourceGroupFromId(id: string): string {
return id.replace(RegExp('/providers/.*'), '').toLowerCase(); return id.replace(RegExp('/providers/.*'), '').toLowerCase();
} }
export function getResourceName(id: string): string {
const splitResourceId = id.split('/');
return splitResourceId[splitResourceId.length - 1];
}
export function getBlobContainerId(resourceGroupId: string, storageAccountName: string, blobContainerName: string): string {
return `${resourceGroupId}/providers/Microsoft.Storage/storageAccounts/${storageAccountName}/blobServices/default/containers/${blobContainerName}`;
}
export interface SqlMigrationServiceProperties { export interface SqlMigrationServiceProperties {
name: string; name: string;
subscriptionId: string; subscriptionId: string;

View File

@@ -3,7 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { MigrationContext } from '../models/migrationLocalStorage'; import { MigrationContext, MigrationStatus } from '../models/migrationLocalStorage';
import { MigrationMode, MigrationTargetType } from '../models/stateMachine';
import * as loc from './strings'; import * as loc from './strings';
export enum SQLTargetAssetType { export enum SQLTargetAssetType {
@@ -22,6 +23,28 @@ export function getMigrationTargetType(migration: MigrationContext): string {
} }
} }
export function getMigrationMode(migration: MigrationContext): string { export function getMigrationTargetTypeEnum(migration: MigrationContext): MigrationTargetType | undefined {
return migration.migrationContext.properties.offlineConfiguration?.offline?.valueOf() ? loc.OFFLINE : loc.OFFLINE; switch (migration.targetManagedInstance.type) {
case SQLTargetAssetType.SQLMI:
return MigrationTargetType.SQLMI;
case SQLTargetAssetType.SQLVM:
return MigrationTargetType.SQLVM;
default:
return undefined;
}
}
export function getMigrationMode(migration: MigrationContext): string {
return migration.migrationContext.properties.offlineConfiguration?.offline?.valueOf() ? loc.OFFLINE : loc.ONLINE;
}
export function getMigrationModeEnum(migration: MigrationContext): MigrationMode {
return migration.migrationContext.properties.offlineConfiguration?.offline?.valueOf() ? MigrationMode.OFFLINE : MigrationMode.ONLINE;
}
export function canRetryMigration(status: string | undefined): boolean {
return status === undefined ||
status === MigrationStatus.Failed ||
status === MigrationStatus.Succeeded ||
status === MigrationStatus.Canceled;
} }

View File

@@ -39,6 +39,7 @@ export class IconPathHelper {
public static newSupportRequest: IconPath; public static newSupportRequest: IconPath;
public static emptyTable: IconPath; public static emptyTable: IconPath;
public static addAzureAccount: IconPath; public static addAzureAccount: IconPath;
public static retry: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) { public static setExtensionContext(context: vscode.ExtensionContext) {
IconPathHelper.copy = { IconPathHelper.copy = {
@@ -153,5 +154,9 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/noAzureAccount.svg'), light: context.asAbsolutePath('images/noAzureAccount.svg'),
dark: context.asAbsolutePath('images/noAzureAccount.svg') dark: context.asAbsolutePath('images/noAzureAccount.svg')
}; };
IconPathHelper.retry = {
light: context.asAbsolutePath('images/retry.svg'),
dark: context.asAbsolutePath('images/retry.svg')
};
} }
} }

View File

@@ -537,7 +537,14 @@ export const AUTHENTICATION_TYPE = localize('sql.migration.authentication.type',
export const REFRESH_BUTTON_LABEL = localize('sql.migration.status.refresh.label', 'Refresh'); export const REFRESH_BUTTON_LABEL = localize('sql.migration.status.refresh.label', 'Refresh');
// Saved Assessment Dialog // Saved Assessment Dialog
export const NEXT_LABEL = localize('sql.migration.saved.assessment.next', "Next"); export const NEXT_LABEL = localize('sql.migration.saved.assessment.next', "Next");
export const CANCEL_LABEL = localize('sql.migration.saved.assessment.cancel', "Cancel"); export const CANCEL_LABEL = localize('sql.migration.saved.assessment.cancel', "Cancel");
export const SAVED_ASSESSMENT_RESULT = localize('sql.migration.saved.assessment.result', "Saved assessment result"); export const SAVED_ASSESSMENT_RESULT = localize('sql.migration.saved.assessment.result', "Saved assessment result");
// Retry Migration
export const MIGRATION_CANNOT_RETRY = localize('sql.migration.cannot.retry', 'Migration cannot be retried.');
export const RETRY_MIGRATION = localize('sql.migration.retry.migration', "Retry migration");
export const MIGRATION_RETRY_ERROR = localize('sql.migration.retry.migration.error', 'An error occurred while retrying the migration.');
export const INVALID_OWNER_URI = localize('sql.migration.invalid.owner.uri.error', 'Cannot connect to the database due to invalid OwnerUri (Parameter \'OwnerUri\')');
export const DATABASE_BACKUP_PAGE_LOAD_ERROR = localize('sql.migration.database.backup.load.error', 'An error occurred while accessing database details.');

View File

@@ -33,6 +33,7 @@ interface StatusCard {
} }
export class DashboardWidget { export class DashboardWidget {
private _context: vscode.ExtensionContext;
private _migrationStatusCardsContainer!: azdata.FlexContainer; private _migrationStatusCardsContainer!: azdata.FlexContainer;
private _migrationStatusCardLoadingContainer!: azdata.LoadingComponent; private _migrationStatusCardLoadingContainer!: azdata.LoadingComponent;
@@ -52,7 +53,8 @@ export class DashboardWidget {
private isRefreshing: boolean = false; private isRefreshing: boolean = false;
constructor() { constructor(context: vscode.ExtensionContext) {
this._context = context;
} }
private async getCurrentMigrations(): Promise<MigrationContext[]> { private async getCurrentMigrations(): Promise<MigrationContext[]> {
@@ -470,7 +472,7 @@ export class DashboardWidget {
this._disposables.push(this._viewAllMigrationsButton.onDidClick(async (e) => { this._disposables.push(this._viewAllMigrationsButton.onDidClick(async (e) => {
const migrationStatus = await this.getCurrentMigrations(); const migrationStatus = await this.getCurrentMigrations();
new MigrationStatusDialog(migrationStatus ? migrationStatus : await this.getMigrations(), AdsMigrationStatus.ALL).initialize(); new MigrationStatusDialog(this._context, migrationStatus ? migrationStatus : await this.getMigrations(), AdsMigrationStatus.ALL).initialize();
})); }));
const refreshButton = view.modelBuilder.hyperlink().withProps({ const refreshButton = view.modelBuilder.hyperlink().withProps({
@@ -596,7 +598,7 @@ export class DashboardWidget {
loc.MIGRATION_IN_PROGRESS loc.MIGRATION_IN_PROGRESS
); );
this._disposables.push(this._inProgressMigrationButton.container.onDidClick(async (e) => { this._disposables.push(this._inProgressMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.ONGOING); const dialog = new MigrationStatusDialog(this._context, await this.getCurrentMigrations(), AdsMigrationStatus.ONGOING);
dialog.initialize(); dialog.initialize();
})); }));
@@ -610,7 +612,7 @@ export class DashboardWidget {
true true
); );
this._disposables.push(this._inProgressWarningMigrationButton.container.onDidClick(async (e) => { this._disposables.push(this._inProgressWarningMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.ONGOING); const dialog = new MigrationStatusDialog(this._context, await this.getCurrentMigrations(), AdsMigrationStatus.ONGOING);
dialog.initialize(); dialog.initialize();
})); }));
@@ -623,7 +625,7 @@ export class DashboardWidget {
loc.MIGRATION_COMPLETED loc.MIGRATION_COMPLETED
); );
this._disposables.push(this._successfulMigrationButton.container.onDidClick(async (e) => { this._disposables.push(this._successfulMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.SUCCEEDED); const dialog = new MigrationStatusDialog(this._context, await this.getCurrentMigrations(), AdsMigrationStatus.SUCCEEDED);
dialog.initialize(); dialog.initialize();
})); }));
this._migrationStatusCardsContainer.addItem( this._migrationStatusCardsContainer.addItem(
@@ -636,7 +638,7 @@ export class DashboardWidget {
loc.MIGRATION_CUTOVER_CARD loc.MIGRATION_CUTOVER_CARD
); );
this._disposables.push(this._completingMigrationButton.container.onDidClick(async (e) => { this._disposables.push(this._completingMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.COMPLETING); const dialog = new MigrationStatusDialog(this._context, await this.getCurrentMigrations(), AdsMigrationStatus.COMPLETING);
dialog.initialize(); dialog.initialize();
})); }));
this._migrationStatusCardsContainer.addItem( this._migrationStatusCardsContainer.addItem(
@@ -648,7 +650,7 @@ export class DashboardWidget {
loc.MIGRATION_FAILED loc.MIGRATION_FAILED
); );
this._disposables.push(this._failedMigrationButton.container.onDidClick(async (e) => { this._disposables.push(this._failedMigrationButton.container.onDidClick(async (e) => {
const dialog = new MigrationStatusDialog(await this.getCurrentMigrations(), AdsMigrationStatus.FAILED); const dialog = new MigrationStatusDialog(this._context, await this.getCurrentMigrations(), AdsMigrationStatus.FAILED);
dialog.initialize(); dialog.initialize();
})); }));
this._migrationStatusCardsContainer.addItem( this._migrationStatusCardsContainer.addItem(

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine'; import { MigrationStateModel, MigrationTargetType, Page } from '../../models/stateMachine';
import { SqlDatabaseTree } from './sqlDatabasesTree'; import { SqlDatabaseTree } from './sqlDatabasesTree';
import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql'; import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage'; import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
@@ -32,7 +32,7 @@ export class AssessmentResultsDialog {
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private _skuRecommendationPage: SKURecommendationPage, private _targetType: MigrationTargetType) { constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private _skuRecommendationPage: SKURecommendationPage, private _targetType: MigrationTargetType) {
this._model = model; this._model = model;
if (this._model.resumeAssessment && this._model.savedInfo.closedPage >= 2) { if (this._model.resumeAssessment && this._model.savedInfo.closedPage >= Page.DatabaseBackup) {
this._model._databaseAssessment = <string[]>this._model.savedInfo.databaseAssessment; this._model._databaseAssessment = <string[]>this._model.savedInfo.databaseAssessment;
} }
this._tree = new SqlDatabaseTree(this._model, this._targetType); this._tree = new SqlDatabaseTree(this._model, this._targetType);

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql'; import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine'; import { MigrationStateModel, MigrationTargetType, Page } from '../../models/stateMachine';
import * as constants from '../../constants/strings'; import * as constants from '../../constants/strings';
import { debounce } from '../../api/utils'; import { debounce } from '../../api/utils';
import { IconPath, IconPathHelper } from '../../constants/iconPathHelper'; import { IconPath, IconPathHelper } from '../../constants/iconPathHelper';
@@ -142,7 +142,7 @@ export class SqlDatabaseTree {
...styles.BOLD_NOTE_CSS, ...styles.BOLD_NOTE_CSS,
'margin': '0px 15px 0px 15px' 'margin': '0px 15px 0px 15px'
}, },
value: constants.DATABASES(0, this._model._databaseAssessment.length) value: constants.DATABASES(0, this._model._databaseAssessment?.length)
}).component(); }).component();
return this._databaseCount; return this._databaseCount;
} }
@@ -187,10 +187,7 @@ export class SqlDatabaseTree {
).component(); ).component();
this._disposables.push(this._databaseTable.onDataChanged(async () => { this._disposables.push(this._databaseTable.onDataChanged(async () => {
await this._databaseCount.updateProperties({ await this.updateValuesOnSelection();
'value': constants.DATABASES(this.selectedDbs().length, this._model._databaseAssessment.length)
});
this._model._databaseSelection = <azdata.DeclarativeTableCellValue[][]>this._databaseTable.dataValues;
})); }));
this._disposables.push(this._databaseTable.onRowSelected(async (e) => { this._disposables.push(this._databaseTable.onRowSelected(async (e) => {
@@ -200,7 +197,7 @@ export class SqlDatabaseTree {
this._activeIssues = []; this._activeIssues = [];
} }
this._dbName.value = this._dbNames[e.row]; this._dbName.value = this._dbNames[e.row];
this._recommendationTitle.value = constants.ISSUES_COUNT(this._activeIssues.length); this._recommendationTitle.value = constants.ISSUES_COUNT(this._activeIssues?.length);
this._recommendation.value = constants.ISSUES_DETAILS; this._recommendation.value = constants.ISSUES_DETAILS;
await this._resultComponent.updateCssStyles({ await this._resultComponent.updateCssStyles({
'display': 'block' 'display': 'block'
@@ -307,7 +304,7 @@ export class SqlDatabaseTree {
'display': 'none' 'display': 'none'
}); });
this._recommendation.value = constants.WARNINGS_DETAILS; this._recommendation.value = constants.WARNINGS_DETAILS;
this._recommendationTitle.value = constants.WARNINGS_COUNT(this._activeIssues.length); this._recommendationTitle.value = constants.WARNINGS_COUNT(this._activeIssues?.length);
if (this._targetType === MigrationTargetType.SQLMI) { if (this._targetType === MigrationTargetType.SQLMI) {
await this.refreshResults(); await this.refreshResults();
} }
@@ -424,7 +421,7 @@ export class SqlDatabaseTree {
} }
private handleFailedAssessment(): boolean { private handleFailedAssessment(): boolean {
const failedAssessment: boolean = this._model._assessmentResults.assessmentError !== undefined const failedAssessment: boolean = this._model._assessmentResults?.assessmentError !== undefined
|| (this._model._assessmentResults?.errors?.length || 0) > 0; || (this._model._assessmentResults?.errors?.length || 0) > 0;
if (failedAssessment) { if (failedAssessment) {
this._dialog.message = { this._dialog.message = {
@@ -439,12 +436,12 @@ export class SqlDatabaseTree {
private getAssessmentError(): string { private getAssessmentError(): string {
const errors: string[] = []; const errors: string[] = [];
const assessmentError = this._model._assessmentResults.assessmentError; const assessmentError = this._model._assessmentResults?.assessmentError;
if (assessmentError) { if (assessmentError) {
errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`); errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`);
} }
if (this._model?._assessmentResults?.errors?.length! > 0) { if (this._model?._assessmentResults?.errors?.length! > 0) {
errors.push(...this._model._assessmentResults.errors?.map( errors.push(...this._model._assessmentResults?.errors?.map(
e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!); e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!);
} }
@@ -791,7 +788,7 @@ export class SqlDatabaseTree {
public async refreshResults(): Promise<void> { public async refreshResults(): Promise<void> {
if (this._targetType === MigrationTargetType.SQLMI) { if (this._targetType === MigrationTargetType.SQLMI) {
if (this._activeIssues.length === 0) { if (this._activeIssues?.length === 0) {
/// show no issues here /// show no issues here
await this._assessmentsTable.updateCssStyles({ await this._assessmentsTable.updateCssStyles({
'display': 'none', 'display': 'none',
@@ -858,7 +855,7 @@ export class SqlDatabaseTree {
|| []; || [];
await this._assessmentResultsTable.setDataValues(assessmentResults); await this._assessmentResultsTable.setDataValues(assessmentResults);
this._assessmentResultsTable.selectedRow = assessmentResults.length > 0 ? 0 : -1; this._assessmentResultsTable.selectedRow = assessmentResults?.length > 0 ? 0 : -1;
} }
public async refreshAssessmentDetails(selectedIssue?: SqlMigrationAssessmentResultItem): Promise<void> { public async refreshAssessmentDetails(selectedIssue?: SqlMigrationAssessmentResultItem): Promise<void> {
@@ -872,7 +869,7 @@ export class SqlDatabaseTree {
await this._impactedObjectsTable.setDataValues(this._impactedObjects.map( await this._impactedObjectsTable.setDataValues(this._impactedObjects.map(
(object) => [{ value: object.objectType }, { value: object.name }])); (object) => [{ value: object.objectType }, { value: object.name }]));
this._impactedObjectsTable.selectedRow = this._impactedObjects.length > 0 ? 0 : -1; this._impactedObjectsTable.selectedRow = this._impactedObjects?.length > 0 ? 0 : -1;
} }
public refreshImpactedObject(impactedObject?: SqlMigrationImpactedObjectInfo): void { public refreshImpactedObject(impactedObject?: SqlMigrationImpactedObjectInfo): void {
@@ -927,17 +924,17 @@ export class SqlDatabaseTree {
style: styleLeft style: styleLeft
}, },
{ {
value: this._model._assessmentResults.issues.length, value: this._model._assessmentResults?.issues?.length,
style: styleRight style: styleRight
} }
] ]
]; ];
this._model._assessmentResults.databaseAssessments.sort((db1, db2) => { this._model._assessmentResults?.databaseAssessments.sort((db1, db2) => {
return db2.issues.length - db1.issues.length; return db2.issues?.length - db1.issues?.length;
}); });
// Reset the dbName list so that it is in sync with the table // Reset the dbName list so that it is in sync with the table
this._dbNames = this._model._assessmentResults.databaseAssessments.map(da => da.name); this._dbNames = this._model._assessmentResults?.databaseAssessments.map(da => da.name);
this._model._assessmentResults.databaseAssessments.forEach((db) => { this._model._assessmentResults?.databaseAssessments.forEach((db) => {
let selectable = true; let selectable = true;
if (db.issues.find(item => item.databaseRestoreFails)) { if (db.issues.find(item => item.databaseRestoreFails)) {
selectable = false; selectable = false;
@@ -954,7 +951,7 @@ export class SqlDatabaseTree {
style: styleLeft style: styleLeft
}, },
{ {
value: db.issues.length, value: db.issues?.length,
style: styleRight style: styleRight
} }
] ]
@@ -962,13 +959,27 @@ export class SqlDatabaseTree {
}); });
} }
await this._instanceTable.setDataValues(instanceTableValues); await this._instanceTable.setDataValues(instanceTableValues);
if (this._model.resumeAssessment && this._model.savedInfo.closedPage >= 2) { if (this._model.resumeAssessment && this._model.savedInfo.closedPage >= Page.SKURecommendation && this._targetType === this._model.savedInfo.migrationTargetType) {
await this._databaseTable.setDataValues(this._model.savedInfo.migrationDatabases); await this._databaseTable.setDataValues(this._model.savedInfo.migrationDatabases);
} else { } else {
if (this._model.retryMigration && this._targetType === this._model.savedInfo.migrationTargetType) {
const sourceDatabaseName = this._model.savedInfo.databaseList[0];
const sourceDatabaseIndex = this._dbNames.indexOf(sourceDatabaseName);
this._databaseTableValues[sourceDatabaseIndex][0].value = true;
}
await this._databaseTable.setDataValues(this._databaseTableValues); await this._databaseTable.setDataValues(this._databaseTableValues);
await this.updateValuesOnSelection();
} }
} }
private async updateValuesOnSelection() {
await this._databaseCount.updateProperties({
'value': constants.DATABASES(this.selectedDbs()?.length, this._model._databaseAssessment?.length)
});
this._model._databaseSelection = <azdata.DeclarativeTableCellValue[][]>this._databaseTable.dataValues;
}
// undo when bug #16445 is fixed // undo when bug #16445 is fixed
private createIconTextCell(icon: IconPath, text: string): string { private createIconTextCell(icon: IconPath, text: string): string {
return text; return text;

View File

@@ -12,15 +12,19 @@ import * as loc from '../../constants/strings';
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage, SupportedAutoRefreshIntervals, clearDialogMessage, displayDialogErrorMessage } from '../../api/utils'; import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage, SupportedAutoRefreshIntervals, clearDialogMessage, displayDialogErrorMessage } from '../../api/utils';
import { EOL } from 'os'; import { EOL } from 'os';
import { ConfirmCutoverDialog } from './confirmCutoverDialog'; import { ConfirmCutoverDialog } from './confirmCutoverDialog';
import { RetryMigrationDialog } from '../retryMigration/retryMigrationDialog';
import * as styles from '../../constants/styles'; import * as styles from '../../constants/styles';
import { canRetryMigration } from '../../constants/helper';
const refreshFrequency: SupportedAutoRefreshIntervals = 30000; const refreshFrequency: SupportedAutoRefreshIntervals = 30000;
const statusImageSize: number = 14; const statusImageSize: number = 14;
export class MigrationCutoverDialog { export class MigrationCutoverDialog {
private _context: vscode.ExtensionContext;
private _dialogObject!: azdata.window.Dialog; private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView; private _view!: azdata.ModelView;
private _model: MigrationCutoverDialogModel; private _model: MigrationCutoverDialogModel;
private _migration: MigrationContext;
private _databaseTitleName!: azdata.TextComponent; private _databaseTitleName!: azdata.TextComponent;
private _cutoverButton!: azdata.ButtonComponent; private _cutoverButton!: azdata.ButtonComponent;
@@ -29,6 +33,7 @@ export class MigrationCutoverDialog {
private _refreshLoader!: azdata.LoadingComponent; private _refreshLoader!: azdata.LoadingComponent;
private _copyDatabaseMigrationDetails!: azdata.ButtonComponent; private _copyDatabaseMigrationDetails!: azdata.ButtonComponent;
private _newSupportRequest!: azdata.ButtonComponent; private _newSupportRequest!: azdata.ButtonComponent;
private _retryButton!: azdata.ButtonComponent;
private _sourceDatabaseInfoField!: InfoFieldSchema; private _sourceDatabaseInfoField!: InfoFieldSchema;
private _sourceDetailsInfoField!: InfoFieldSchema; private _sourceDetailsInfoField!: InfoFieldSchema;
@@ -53,7 +58,9 @@ export class MigrationCutoverDialog {
readonly _infoFieldWidth: string = '250px'; readonly _infoFieldWidth: string = '250px';
constructor(migration: MigrationContext) { constructor(context: vscode.ExtensionContext, migration: MigrationContext) {
this._context = context;
this._migration = migration;
this._model = new MigrationCutoverDialogModel(migration); this._model = new MigrationCutoverDialogModel(migration);
this._dialogObject = azdata.window.createModelViewDialog('', 'MigrationCutoverDialog', 'wide'); this._dialogObject = azdata.window.createModelViewDialog('', 'MigrationCutoverDialog', 'wide');
} }
@@ -301,11 +308,11 @@ export class MigrationCutoverDialog {
iconWidth: '16px', iconWidth: '16px',
label: loc.COMPLETE_CUTOVER, label: loc.COMPLETE_CUTOVER,
height: '20px', height: '20px',
width: '150px', width: '140px',
enabled: false, enabled: false,
CSSStyles: { CSSStyles: {
...styles.BODY_CSS, ...styles.BODY_CSS,
'display': this._isOnlineMigration() ? 'inline' : 'none' 'display': this._isOnlineMigration() ? 'block' : 'none'
} }
}).component(); }).component();
@@ -330,7 +337,7 @@ export class MigrationCutoverDialog {
iconWidth: '16px', iconWidth: '16px',
label: loc.CANCEL_MIGRATION, label: loc.CANCEL_MIGRATION,
height: '20px', height: '20px',
width: '150px', width: '140px',
enabled: false, enabled: false,
CSSStyles: { CSSStyles: {
...styles.BODY_CSS, ...styles.BODY_CSS,
@@ -353,6 +360,28 @@ export class MigrationCutoverDialog {
flex: '0' flex: '0'
}); });
this._retryButton = this._view.modelBuilder.button().withProps({
label: loc.RETRY_MIGRATION,
iconPath: IconPathHelper.retry,
enabled: false,
iconHeight: '16px',
iconWidth: '16px',
height: '20px',
width: '120px',
CSSStyles: {
...styles.BODY_CSS,
}
}).component();
this._disposables.push(this._retryButton.onDidClick(
async (e) => {
await this.refreshStatus();
let retryMigrationDialog = new RetryMigrationDialog(this._context, this._migration);
await retryMigrationDialog.openDialog();
}
));
headerActions.addItem(this._retryButton, {
flex: '0',
});
this._refreshButton = this._view.modelBuilder.button().withProps({ this._refreshButton = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.refresh, iconPath: IconPathHelper.refresh,
@@ -360,7 +389,7 @@ export class MigrationCutoverDialog {
iconWidth: '16px', iconWidth: '16px',
label: 'Refresh', label: 'Refresh',
height: '20px', height: '20px',
width: '100px', width: '80px',
CSSStyles: { CSSStyles: {
...styles.BODY_CSS, ...styles.BODY_CSS,
} }
@@ -379,7 +408,7 @@ export class MigrationCutoverDialog {
iconWidth: '16px', iconWidth: '16px',
label: loc.COPY_MIGRATION_DETAILS, label: loc.COPY_MIGRATION_DETAILS,
height: '20px', height: '20px',
width: '200px', width: '160px',
CSSStyles: { CSSStyles: {
...styles.BODY_CSS, ...styles.BODY_CSS,
} }
@@ -406,7 +435,7 @@ export class MigrationCutoverDialog {
iconHeight: '16px', iconHeight: '16px',
iconWidth: '16px', iconWidth: '16px',
height: '20px', height: '20px',
width: '180px', width: '160px',
CSSStyles: { CSSStyles: {
...styles.BODY_CSS, ...styles.BODY_CSS,
} }
@@ -567,7 +596,7 @@ export class MigrationCutoverDialog {
if (this._isOnlineMigration()) { if (this._isOnlineMigration()) {
await this._cutoverButton.updateCssStyles({ await this._cutoverButton.updateCssStyles({
'display': 'inline' 'display': 'block'
}); });
} }
@@ -720,6 +749,9 @@ export class MigrationCutoverDialog {
this._cancelButton.enabled = this._cancelButton.enabled =
migrationStatusTextValue === MigrationStatus.Creating || migrationStatusTextValue === MigrationStatus.Creating ||
migrationStatusTextValue === MigrationStatus.InProgress; migrationStatusTextValue === MigrationStatus.InProgress;
this._retryButton.enabled = canRetryMigration(migrationStatusTextValue);
} catch (e) { } catch (e) {
displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_STATUS_REFRESH_ERROR, e); displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_STATUS_REFRESH_ERROR, e);
console.log(e); console.log(e);

View File

@@ -14,7 +14,8 @@ import { clearDialogMessage, convertTimeDifferenceToDuration, displayDialogError
import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog'; import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog';
import { ConfirmCutoverDialog } from '../migrationCutover/confirmCutoverDialog'; import { ConfirmCutoverDialog } from '../migrationCutover/confirmCutoverDialog';
import { MigrationCutoverDialogModel } from '../migrationCutover/migrationCutoverDialogModel'; import { MigrationCutoverDialogModel } from '../migrationCutover/migrationCutoverDialogModel';
import { getMigrationTargetType, getMigrationMode } from '../../constants/helper'; import { getMigrationTargetType, getMigrationMode, canRetryMigration } from '../../constants/helper';
import { RetryMigrationDialog } from '../retryMigration/retryMigrationDialog';
const refreshFrequency: SupportedAutoRefreshIntervals = 180000; const refreshFrequency: SupportedAutoRefreshIntervals = 180000;
@@ -29,9 +30,11 @@ const MenuCommands = {
ViewService: 'sqlmigration.view.service', ViewService: 'sqlmigration.view.service',
CopyMigration: 'sqlmigration.copy.migration', CopyMigration: 'sqlmigration.copy.migration',
CancelMigration: 'sqlmigration.cancel.migration', CancelMigration: 'sqlmigration.cancel.migration',
RetryMigration: 'sqlmigration.retry.migration',
}; };
export class MigrationStatusDialog { export class MigrationStatusDialog {
private _context: vscode.ExtensionContext;
private _model: MigrationStatusDialogModel; private _model: MigrationStatusDialogModel;
private _dialogObject!: azdata.window.Dialog; private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView; private _view!: azdata.ModelView;
@@ -45,7 +48,8 @@ export class MigrationStatusDialog {
private isRefreshing = false; private isRefreshing = false;
constructor(migrations: MigrationContext[], private _filter: AdsMigrationStatus) { constructor(context: vscode.ExtensionContext, migrations: MigrationContext[], private _filter: AdsMigrationStatus) {
this._context = context;
this._model = new MigrationStatusDialogModel(migrations); this._model = new MigrationStatusDialogModel(migrations);
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide'); this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide');
} }
@@ -221,7 +225,7 @@ export class MigrationStatusDialog {
async (migrationId: string) => { async (migrationId: string) => {
try { try {
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId); const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
const dialog = new MigrationCutoverDialog(migration!); const dialog = new MigrationCutoverDialog(this._context, migration!);
await dialog.initialize(); await dialog.initialize();
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@@ -302,6 +306,25 @@ export class MigrationStatusDialog {
console.log(e); console.log(e);
} }
})); }));
this._disposables.push(vscode.commands.registerCommand(
MenuCommands.RetryMigration,
async (migrationId: string) => {
try {
clearDialogMessage(this._dialogObject);
const migration = this._model._migrations.find(migration => migration.migrationContext.id === migrationId);
if (canRetryMigration(migration?.migrationContext.properties.migrationStatus)) {
let retryMigrationDialog = new RetryMigrationDialog(this._context, migration!);
await retryMigrationDialog.openDialog();
}
else {
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_RETRY);
}
} catch (e) {
displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_RETRY_ERROR, e);
console.log(e);
}
}));
} }
private async populateMigrationTable(): Promise<void> { private async populateMigrationTable(): Promise<void> {
@@ -366,7 +389,7 @@ export class MigrationStatusDialog {
}).component(); }).component();
this._disposables.push(databaseHyperLink.onDidClick( this._disposables.push(databaseHyperLink.onDidClick(
async (e) => await (new MigrationCutoverDialog(migration)).initialize())); async (e) => await (new MigrationCutoverDialog(this._context, migration)).initialize()));
return this._view.modelBuilder return this._view.modelBuilder
.flexContainer() .flexContainer()
@@ -416,6 +439,10 @@ export class MigrationStatusDialog {
menuCommands.push(MenuCommands.CancelMigration); menuCommands.push(MenuCommands.CancelMigration);
} }
if (canRetryMigration(migrationStatus)) {
menuCommands.push(MenuCommands.RetryMigration);
}
return menuCommands; return menuCommands;
} }

View File

@@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as mssql from '../../../../mssql';
import { azureResource } from 'azureResource';
import { getLocations, getResourceGroupFromId, getBlobContainerId, getFullResourceGroupFromId, getResourceName } from '../../api/azure';
import { MigrationMode, MigrationStateModel, NetworkContainerType, SavedInfo, Page } from '../../models/stateMachine';
import { MigrationContext } from '../../models/migrationLocalStorage';
import { WizardController } from '../../wizard/wizardController';
import { getMigrationModeEnum, getMigrationTargetTypeEnum } from '../../constants/helper';
export class RetryMigrationDialog {
private _context: vscode.ExtensionContext;
private _migration: MigrationContext;
constructor(context: vscode.ExtensionContext, migration: MigrationContext) {
this._context = context;
this._migration = migration;
}
private createMigrationStateModel(migration: MigrationContext, connectionId: string, serverName: string, api: mssql.IExtension, location: azureResource.AzureLocation): MigrationStateModel {
let stateModel = new MigrationStateModel(this._context, connectionId, api.sqlMigration);
const sourceDatabaseName = migration.migrationContext.properties.sourceDatabaseName;
let savedInfo: SavedInfo;
savedInfo = {
closedPage: Page.AzureAccount,
// AzureAccount
azureAccount: migration.azureAccount,
azureTenant: migration.azureAccount.properties.tenants[0],
// DatabaseSelector
selectedDatabases: [],
// SKURecommendation
databaseAssessment: [],
databaseList: [sourceDatabaseName],
migrationDatabases: [],
serverAssessment: null,
migrationTargetType: getMigrationTargetTypeEnum(migration)!,
subscription: migration.subscription,
location: location,
resourceGroup: {
id: getFullResourceGroupFromId(migration.targetManagedInstance.id),
name: getResourceGroupFromId(migration.targetManagedInstance.id),
subscription: migration.subscription
},
targetServerInstance: migration.targetManagedInstance,
// MigrationMode
migrationMode: getMigrationModeEnum(migration),
// DatabaseBackup
targetSubscription: migration.subscription,
targetDatabaseNames: [migration.migrationContext.name],
networkContainerType: null,
networkShare: null,
blobs: [],
// Integration Runtime
migrationServiceId: migration.migrationContext.properties.migrationService,
};
const getStorageAccountResourceGroup = (storageAccountResourceId: string) => {
return {
id: getFullResourceGroupFromId(storageAccountResourceId!),
name: getResourceGroupFromId(storageAccountResourceId!),
subscription: migration.subscription
};
};
const getStorageAccount = (storageAccountResourceId: string) => {
const storageAccountName = getResourceName(storageAccountResourceId);
return {
type: 'microsoft.storage/storageaccounts',
id: storageAccountResourceId!,
tenantId: savedInfo.azureTenant?.id!,
subscriptionId: migration.subscription.id,
name: storageAccountName,
location: savedInfo.location!.name,
};
};
const sourceLocation = migration.migrationContext.properties.backupConfiguration.sourceLocation;
if (sourceLocation?.fileShare) {
savedInfo.networkContainerType = NetworkContainerType.NETWORK_SHARE;
const storageAccountResourceId = migration.migrationContext.properties.backupConfiguration.targetLocation?.storageAccountResourceId!;
savedInfo.networkShare = {
password: '',
networkShareLocation: sourceLocation?.fileShare?.path!,
windowsUser: sourceLocation?.fileShare?.username!,
storageAccount: getStorageAccount(storageAccountResourceId!),
resourceGroup: getStorageAccountResourceGroup(storageAccountResourceId!),
storageKey: ''
};
} else if (sourceLocation?.azureBlob) {
savedInfo.networkContainerType = NetworkContainerType.BLOB_CONTAINER;
const storageAccountResourceId = sourceLocation?.azureBlob?.storageAccountResourceId!;
savedInfo.blobs = [
{
blobContainer: {
id: getBlobContainerId(getFullResourceGroupFromId(storageAccountResourceId!), getResourceName(storageAccountResourceId!), sourceLocation?.azureBlob.blobContainerName),
name: sourceLocation?.azureBlob.blobContainerName,
subscription: migration.subscription
},
lastBackupFile: getMigrationModeEnum(migration) === MigrationMode.OFFLINE ? migration.migrationContext.properties.offlineConfiguration.lastBackupName! : undefined,
storageAccount: getStorageAccount(storageAccountResourceId!),
resourceGroup: getStorageAccountResourceGroup(storageAccountResourceId!),
storageKey: ''
}
];
}
stateModel.retryMigration = true;
stateModel.savedInfo = savedInfo;
stateModel.serverName = serverName;
return stateModel;
}
public async openDialog(dialogName?: string) {
const locations = await getLocations(this._migration.azureAccount, this._migration.subscription);
let location: azureResource.AzureLocation;
locations.forEach(azureLocation => {
if (azureLocation.name === this._migration.targetManagedInstance.location) {
location = azureLocation;
}
});
let activeConnection = await azdata.connection.getCurrentConnection();
let connectionId: string = '';
let serverName: string = '';
if (!activeConnection) {
const connection = await azdata.connection.openConnectionDialog();
if (connection) {
connectionId = connection.connectionId;
serverName = connection.options.server;
}
} else {
connectionId = activeConnection.connectionId;
serverName = activeConnection.serverName;
}
const api = (await vscode.extensions.getExtension(mssql.extension.name)?.activate()) as mssql.IExtension;
const stateModel = this.createMigrationStateModel(this._migration, connectionId, serverName, api, location!);
const wizardController = new WizardController(this._context, stateModel);
await wizardController.openWizard(stateModel.sourceConnectionId);
}
}

View File

@@ -108,11 +108,7 @@ class SQLMigration {
await wizardController.openWizard(connectionId); await wizardController.openWizard(connectionId);
} }
} }
} }
} }
private checkSavedInfo(serverName: string): SavedInfo | undefined { private checkSavedInfo(serverName: string): SavedInfo | undefined {
@@ -138,7 +134,7 @@ let sqlMigration: SQLMigration;
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
sqlMigration = new SQLMigration(context); sqlMigration = new SQLMigration(context);
await sqlMigration.registerCommands(); await sqlMigration.registerCommands();
let widget = new DashboardWidget(); let widget = new DashboardWidget(context);
widget.register(); widget.register();
} }

View File

@@ -71,6 +71,7 @@ export enum Page {
export enum WizardEntryPoint { export enum WizardEntryPoint {
Default = 'Default', Default = 'Default',
SaveAndClose = 'SaveAndClose', SaveAndClose = 'SaveAndClose',
RetryMigration = 'RetryMigration',
} }
export interface DatabaseBackupModel { export interface DatabaseBackupModel {
@@ -188,6 +189,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public refreshDatabaseBackupPage!: boolean; public refreshDatabaseBackupPage!: boolean;
public _databaseSelection!: azdata.DeclarativeTableCellValue[][]; public _databaseSelection!: azdata.DeclarativeTableCellValue[][];
public retryMigration!: boolean;
public resumeAssessment!: boolean; public resumeAssessment!: boolean;
public savedInfo!: SavedInfo; public savedInfo!: SavedInfo;
public closedPage!: number; public closedPage!: number;
@@ -293,7 +295,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
private async generateAssessmentTelemetry(): Promise<void> { private async generateAssessmentTelemetry(): Promise<void> {
try { try {
let serverIssues = this._assessmentResults.issues.map(i => { let serverIssues = this._assessmentResults?.issues.map(i => {
return { return {
ruleId: i.ruleId, ruleId: i.ruleId,
count: i.impactedObjects.length count: i.impactedObjects.length
@@ -337,10 +339,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
'serverErrors': JSON.stringify(serverErrors), 'serverErrors': JSON.stringify(serverErrors),
}, },
{ {
'issuesCount': this._assessmentResults.issues.length, 'issuesCount': this._assessmentResults?.issues.length,
'warningsCount': this._assessmentResults.databaseAssessments.reduce((count, d) => count + d.issues.length, 0), 'warningsCount': this._assessmentResults?.databaseAssessments.reduce((count, d) => count + d.issues.length, 0),
'durationInMilliseconds': endTime.getTime() - startTime.getTime(), 'durationInMilliseconds': endTime.getTime() - startTime.getTime(),
'databaseCount': this._assessmentResults.databaseAssessments.length, 'databaseCount': this._assessmentResults?.databaseAssessments.length,
'serverHostCpuCount': this._assessmentApiResponse?.assessmentResult?.cpuCoreCount, 'serverHostCpuCount': this._assessmentApiResponse?.assessmentResult?.cpuCoreCount,
'serverHostPhysicalMemoryInBytes': this._assessmentApiResponse?.assessmentResult?.physicalServerMemory, 'serverHostPhysicalMemoryInBytes': this._assessmentApiResponse?.assessmentResult?.physicalServerMemory,
'serverDatabases': this._assessmentApiResponse?.assessmentResult?.numberOfUserDatabases, 'serverDatabases': this._assessmentApiResponse?.assessmentResult?.numberOfUserDatabases,
@@ -626,12 +628,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getManagedInstanceValues(subscription: azureResource.AzureResourceSubscription, location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> { public async getManagedInstanceValues(subscription: azureResource.AzureResourceSubscription, location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let managedInstanceValues: azdata.CategoryValue[] = []; let managedInstanceValues: azdata.CategoryValue[] = [];
if (!this._azureAccount) { if (!this._azureAccount || !subscription) {
return managedInstanceValues; return managedInstanceValues;
} }
try { try {
this._targetManagedInstances = (await getAvailableManagedInstanceProducts(this._azureAccount, subscription)).filter((mi) => { this._targetManagedInstances = (await getAvailableManagedInstanceProducts(this._azureAccount, subscription)).filter((mi) => {
if (mi.location.toLowerCase() === location.name.toLowerCase() && mi.resourceGroup?.toLowerCase() === resourceGroup?.name.toLowerCase()) { if (mi.location.toLowerCase() === location?.name.toLowerCase() && mi.resourceGroup?.toLowerCase() === resourceGroup?.name.toLowerCase()) {
return true; return true;
} }
return false; return false;
@@ -678,7 +680,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
try { try {
if (this._azureAccount && subscription && resourceGroup) { if (this._azureAccount && subscription && resourceGroup) {
this._targetSqlVirtualMachines = (await getAvailableSqlVMs(this._azureAccount, subscription, resourceGroup)).filter((virtualMachine) => { this._targetSqlVirtualMachines = (await getAvailableSqlVMs(this._azureAccount, subscription, resourceGroup)).filter((virtualMachine) => {
if (virtualMachine.location === location.name) { if (virtualMachine?.location?.toLowerCase() === location?.name?.toLowerCase()) {
if (virtualMachine.properties.sqlImageOffer) { if (virtualMachine.properties.sqlImageOffer) {
return virtualMachine.properties.sqlImageOffer.toLowerCase().includes('-ws'); //filtering out all non windows sql vms. return virtualMachine.properties.sqlImageOffer.toLowerCase().includes('-ws'); //filtering out all non windows sql vms.
} }
@@ -996,6 +998,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
let wizardEntryPoint = WizardEntryPoint.Default; let wizardEntryPoint = WizardEntryPoint.Default;
if (this.resumeAssessment) { if (this.resumeAssessment) {
wizardEntryPoint = WizardEntryPoint.SaveAndClose; wizardEntryPoint = WizardEntryPoint.SaveAndClose;
} else if (this.retryMigration) {
wizardEntryPoint = WizardEntryPoint.RetryMigration;
} }
if (response.status === 201 || response.status === 200) { if (response.status === 201 || response.status === 200) {
sendSqlMigrationActionEvent( sendSqlMigrationActionEvent(

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; import { MigrationStateModel, Page, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import { deepClone, findDropDownItemIndex, selectDropDownIndex } from '../api/utils'; import { deepClone, findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
@@ -111,9 +111,9 @@ export class AccountsSelectionPage extends MigrationWizardPage {
await this._accountTenantFlexContainer.updateCssStyles({ await this._accountTenantFlexContainer.updateCssStyles({
'display': 'none' 'display': 'none'
}); });
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 0) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.AzureAccount)) {
(<azdata.CategoryValue[]>this._azureAccountsDropdown.values)?.forEach((account, index) => { (<azdata.CategoryValue[]>this._azureAccountsDropdown.values)?.forEach((account, index) => {
if (account.name === this.migrationStateModel.savedInfo.azureAccount?.displayInfo.userId) { if (account.name.toLowerCase() === this.migrationStateModel.savedInfo.azureAccount?.displayInfo.userId.toLowerCase()) {
selectDropDownIndex(this._azureAccountsDropdown, index); selectDropDownIndex(this._azureAccountsDropdown, index);
} }
}); });

View File

@@ -283,7 +283,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
} }
return true; return true;
}).component(); }).component();
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode)) {
this._networkSharePath.value = this.migrationStateModel.savedInfo.networkShare?.networkShareLocation; this._networkSharePath.value = this.migrationStateModel.savedInfo.networkShare?.networkShareLocation;
} }
this._disposables.push(this._networkSharePath.onTextChanged(async (value) => { this._disposables.push(this._networkSharePath.onTextChanged(async (value) => {
@@ -331,7 +331,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
} }
return true; return true;
}).component(); }).component();
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
this._windowsUserAccountText.value = this.migrationStateModel.savedInfo.networkShare?.windowsUser; this._windowsUserAccountText.value = this.migrationStateModel.savedInfo.networkShare?.windowsUser;
} }
this._disposables.push(this._windowsUserAccountText.onTextChanged((value) => { this._disposables.push(this._windowsUserAccountText.onTextChanged((value) => {
@@ -458,7 +458,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return flexContainer; return flexContainer;
} }
private createTargetDatabaseContainer(): azdata.FlexContainer { private createTargetDatabaseContainer(): azdata.FlexContainer {
const headerCssStyles: azdata.CssStyles = { const headerCssStyles: azdata.CssStyles = {
...styles.LABEL_CSS, ...styles.LABEL_CSS,
@@ -755,253 +754,265 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return container; return container;
} }
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> { public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this.migrationStateModel.refreshDatabaseBackupPage) { if (this.migrationStateModel.refreshDatabaseBackupPage) {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) { try {
this.migrationStateModel._migrationDbs = this.migrationStateModel.savedInfo.databaseList; if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
} this.migrationStateModel._migrationDbs = this.migrationStateModel.savedInfo.databaseList;
const isOfflineMigration = this.migrationStateModel._databaseBackup?.migrationMode === MigrationMode.OFFLINE; }
const lastBackupFileColumnIndex = this._blobContainerTargetDatabaseNamesTable.columns.length - 1; const isOfflineMigration = this.migrationStateModel._databaseBackup?.migrationMode === MigrationMode.OFFLINE;
this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = !isOfflineMigration; const lastBackupFileColumnIndex = this._blobContainerTargetDatabaseNamesTable.columns.length - 1;
this._blobContainerTargetDatabaseNamesTable.columns.forEach(column => { this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = !isOfflineMigration;
column.width = isOfflineMigration ? WIZARD_TABLE_COLUMN_WIDTH_SMALL : WIZARD_TABLE_COLUMN_WIDTH; this._blobContainerTargetDatabaseNamesTable.columns.forEach(column => {
}); column.width = isOfflineMigration ? WIZARD_TABLE_COLUMN_WIDTH_SMALL : WIZARD_TABLE_COLUMN_WIDTH;
});
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode)) {
if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) { if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
this._networkShareButton.checked = true; this._networkShareButton.checked = true;
} else {
this._networkShareButton.checked = false;
this._networkTableContainer.display = 'none';
await this._networkShareContainer.updateCssStyles({ 'display': 'none' });
}
} else { } else {
this._networkShareButton.checked = false; this._networkShareButton.checked = false;
this._networkTableContainer.display = 'none'; this._networkTableContainer.display = 'none';
await this._networkShareContainer.updateCssStyles({ 'display': 'none' }); await this._networkShareContainer.updateCssStyles({ 'display': 'none' });
} }
} else {
this._networkShareButton.checked = false;
this._networkTableContainer.display = 'none';
await this._networkShareContainer.updateCssStyles({ 'display': 'none' });
}
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode)) {
if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.BLOB_CONTAINER) { if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
this._blobContainerButton.checked = true; this._blobContainerButton.checked = true;
} else {
this._blobContainerButton.checked = false;
this._blobTableContainer.display = 'none';
await this._blobContainer.updateCssStyles({ 'display': 'none' });
}
} else { } else {
this._blobContainerButton.checked = false; this._blobContainerButton.checked = false;
this._blobTableContainer.display = 'none'; this._blobTableContainer.display = 'none';
await this._blobContainer.updateCssStyles({ 'display': 'none' }); await this._blobContainer.updateCssStyles({ 'display': 'none' });
} }
} else {
this._blobContainerButton.checked = false;
this._blobTableContainer.display = 'none';
await this._blobContainer.updateCssStyles({ 'display': 'none' });
}
await this._targetDatabaseContainer.updateCssStyles({ 'display': 'none' }); await this._targetDatabaseContainer.updateCssStyles({ 'display': 'none' });
await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' }); await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' });
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile(); const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider); const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
const query = 'select SUSER_NAME()'; const query = 'select SUSER_NAME()';
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query); const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
const username = results.rows[0][0].displayValue; const username = results.rows[0][0].displayValue;
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === 'SqlLogin' ? MigrationSourceAuthenticationType.Sql : connectionProfile.authenticationType === 'Integrated' ? MigrationSourceAuthenticationType.Integrated : undefined!; this.migrationStateModel._authenticationType = connectionProfile.authenticationType === 'SqlLogin' ? MigrationSourceAuthenticationType.Sql : connectionProfile.authenticationType === 'Integrated' ? MigrationSourceAuthenticationType.Integrated : undefined!;
this._sourceHelpText.value = constants.SQL_SOURCE_DETAILS(this.migrationStateModel._authenticationType, connectionProfile.serverName); this._sourceHelpText.value = constants.SQL_SOURCE_DETAILS(this.migrationStateModel._authenticationType, connectionProfile.serverName);
this._sqlSourceUsernameInput.value = username; this._sqlSourceUsernameInput.value = username;
this._sqlSourcePassword.value = (await azdata.connection.getCredentials(this.migrationStateModel.sourceConnectionId)).password; this._sqlSourcePassword.value = (await azdata.connection.getCredentials(this.migrationStateModel.sourceConnectionId)).password;
this._networkShareTargetDatabaseNames = []; this._networkShareTargetDatabaseNames = [];
this._blobContainerTargetDatabaseNames = []; this._blobContainerTargetDatabaseNames = [];
this._blobContainerResourceGroupDropdowns = []; this._blobContainerResourceGroupDropdowns = [];
this._blobContainerStorageAccountDropdowns = []; this._blobContainerStorageAccountDropdowns = [];
this._blobContainerDropdowns = []; this._blobContainerDropdowns = [];
this._blobContainerLastBackupFileDropdowns = []; this._blobContainerLastBackupFileDropdowns = [];
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) { if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
this._existingDatabases = await this.migrationStateModel.getManagedDatabases(); this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
}
this.migrationStateModel._targetDatabaseNames = [];
this.migrationStateModel._databaseBackup.blobs = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
this.migrationStateModel._targetDatabaseNames.push('');
this.migrationStateModel._databaseBackup.blobs.push(<Blob>{});
const targetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
required: true,
value: db,
width: WIZARD_TABLE_COLUMN_WIDTH
}).withValidation(c => {
if (this._networkShareTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
c.validationErrorMessage = constants.DUPLICATE_NAME_ERROR;
return false;
}
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(c.value!)) { // Making sure if database with same name is not present on the target Azure SQL
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(c.value!, 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();
this._disposables.push(targetDatabaseInput.onTextChanged(async (value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
await this.validateFields();
}));
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) {
targetDatabaseInput.value = this.migrationStateModel.savedInfo.targetDatabaseNames[index];
} else {
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
} }
this._networkShareTargetDatabaseNames.push(targetDatabaseInput); this.migrationStateModel._targetDatabaseNames = [];
this.migrationStateModel._databaseBackup.blobs = [];
const blobTargetDatabaseInput = this._view.modelBuilder.inputBox().withProps({ this.migrationStateModel._migrationDbs.forEach((db, index) => {
required: true, this.migrationStateModel._targetDatabaseNames.push('');
value: db, this.migrationStateModel._databaseBackup.blobs.push(<Blob>{});
}).withValidation(c => { const targetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
if (this._blobContainerTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values. required: true,
c.validationErrorMessage = constants.DUPLICATE_NAME_ERROR; value: db,
return false; width: WIZARD_TABLE_COLUMN_WIDTH
} }).withValidation(c => {
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(c.value!)) { // Making sure if database with same name is not present on the target Azure SQL if (this._networkShareTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(c.value!, this.migrationStateModel._targetServerInstance.name); c.validationErrorMessage = constants.DUPLICATE_NAME_ERROR;
return false; 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();
this._disposables.push(blobTargetDatabaseInput.onTextChanged((value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
}));
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) {
blobTargetDatabaseInput.value = this.migrationStateModel.savedInfo.targetDatabaseNames[index];
} else {
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
}
this._blobContainerTargetDatabaseNames.push(blobTargetDatabaseInput);
const blobContainerResourceDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER_RESOURCE_GROUP,
editable: true,
fireOnTextChange: true,
required: true,
}).component();
const blobContainerStorageAccountDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER_STORAGE_ACCOUNT,
editable: true,
fireOnTextChange: true,
required: true,
enabled: false,
}).component();
const blobContainerDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER,
editable: true,
fireOnTextChange: true,
required: true,
enabled: false,
}).component();
const blobContainerLastBackupFileDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER_LAST_BACKUP_FILE,
editable: true,
fireOnTextChange: true,
required: true,
enabled: false,
}).component();
this._disposables.push(blobContainerResourceDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(blobContainerResourceDropdown, value);
if (selectedIndex > -1 && !blobResourceGroupErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].resourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex);
await this.loadBlobStorageDropdown(index);
await blobContainerStorageAccountDropdown.updateProperties({ enabled: true });
} else {
await this.disableBlobTableDropdowns(index, constants.RESOURCE_GROUP);
}
}));
this._blobContainerResourceGroupDropdowns.push(blobContainerResourceDropdown);
this._disposables.push(blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(blobContainerStorageAccountDropdown, value);
if (selectedIndex > -1 && !blobStorageAccountErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].storageAccount = this.migrationStateModel.getStorageAccount(selectedIndex);
await this.loadBlobContainerDropdown(index);
await blobContainerDropdown.updateProperties({ enabled: true });
} else {
await this.disableBlobTableDropdowns(index, constants.STORAGE_ACCOUNT);
}
}));
this._blobContainerStorageAccountDropdowns.push(blobContainerStorageAccountDropdown);
this._disposables.push(blobContainerDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(blobContainerDropdown, value);
if (selectedIndex > -1 && !blobContainerErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].blobContainer = this.migrationStateModel.getBlobContainer(selectedIndex);
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
await this.loadBlobLastBackupFileDropdown(index);
await blobContainerLastBackupFileDropdown.updateProperties({ enabled: true });
} }
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(c.value!)) { // Making sure if database with same name is not present on the target Azure SQL
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(c.value!, 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();
this._disposables.push(targetDatabaseInput.onTextChanged(async (value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
await this.validateFields();
}));
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
targetDatabaseInput.value = this.migrationStateModel.savedInfo.targetDatabaseNames[index];
} else { } else {
await this.disableBlobTableDropdowns(index, constants.BLOB_CONTAINER); targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
} }
})); this._networkShareTargetDatabaseNames.push(targetDatabaseInput);
this._blobContainerDropdowns.push(blobContainerDropdown);
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) { const blobTargetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
this._disposables.push(blobContainerLastBackupFileDropdown.onValueChanged(value => { required: true,
const selectedIndex = findDropDownItemIndex(blobContainerLastBackupFileDropdown, value); value: db,
if (selectedIndex > -1 && !blobFileErrorStrings.includes(value)) { }).withValidation(c => {
this.migrationStateModel._databaseBackup.blobs[index].lastBackupFile = this.migrationStateModel.getBlobLastBackupFileName(selectedIndex); if (this._blobContainerTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
c.validationErrorMessage = constants.DUPLICATE_NAME_ERROR;
return false;
}
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(c.value!)) { // Making sure if database with same name is not present on the target Azure SQL
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(c.value!, 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();
this._disposables.push(blobTargetDatabaseInput.onTextChanged((value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
}));
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
blobTargetDatabaseInput.value = this.migrationStateModel.savedInfo.targetDatabaseNames[index];
} else {
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
}
this._blobContainerTargetDatabaseNames.push(blobTargetDatabaseInput);
const blobContainerResourceDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER_RESOURCE_GROUP,
editable: true,
fireOnTextChange: true,
required: true,
}).component();
const blobContainerStorageAccountDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER_STORAGE_ACCOUNT,
editable: true,
fireOnTextChange: true,
required: true,
enabled: false,
}).component();
const blobContainerDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER,
editable: true,
fireOnTextChange: true,
required: true,
enabled: false,
}).component();
const blobContainerLastBackupFileDropdown = this._view.modelBuilder.dropDown().withProps({
ariaLabel: constants.BLOB_CONTAINER_LAST_BACKUP_FILE,
editable: true,
fireOnTextChange: true,
required: true,
enabled: false,
}).component();
this._disposables.push(blobContainerResourceDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(blobContainerResourceDropdown, value);
if (selectedIndex > -1 && !blobResourceGroupErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].resourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex);
await this.loadBlobStorageDropdown(index);
await blobContainerStorageAccountDropdown.updateProperties({ enabled: true });
} else {
await this.disableBlobTableDropdowns(index, constants.RESOURCE_GROUP);
} }
})); }));
this._blobContainerLastBackupFileDropdowns.push(blobContainerLastBackupFileDropdown); this._blobContainerResourceGroupDropdowns.push(blobContainerResourceDropdown);
this._disposables.push(blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(blobContainerStorageAccountDropdown, value);
if (selectedIndex > -1 && !blobStorageAccountErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].storageAccount = this.migrationStateModel.getStorageAccount(selectedIndex);
await this.loadBlobContainerDropdown(index);
await blobContainerDropdown.updateProperties({ enabled: true });
} else {
await this.disableBlobTableDropdowns(index, constants.STORAGE_ACCOUNT);
}
}));
this._blobContainerStorageAccountDropdowns.push(blobContainerStorageAccountDropdown);
this._disposables.push(blobContainerDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(blobContainerDropdown, value);
if (selectedIndex > -1 && !blobContainerErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].blobContainer = this.migrationStateModel.getBlobContainer(selectedIndex);
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
await this.loadBlobLastBackupFileDropdown(index);
await blobContainerLastBackupFileDropdown.updateProperties({ enabled: true });
}
} else {
await this.disableBlobTableDropdowns(index, constants.BLOB_CONTAINER);
}
}));
this._blobContainerDropdowns.push(blobContainerDropdown);
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
this._disposables.push(blobContainerLastBackupFileDropdown.onValueChanged(value => {
const selectedIndex = findDropDownItemIndex(blobContainerLastBackupFileDropdown, value);
if (selectedIndex > -1 && !blobFileErrorStrings.includes(value)) {
this.migrationStateModel._databaseBackup.blobs[index].lastBackupFile = this.migrationStateModel.getBlobLastBackupFileName(selectedIndex);
}
}));
this._blobContainerLastBackupFileDropdowns.push(blobContainerLastBackupFileDropdown);
}
});
let data: azdata.DeclarativeTableCellValue[][] = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
targetRow.push({
value: db
});
targetRow.push({
value: this._networkShareTargetDatabaseNames[index]
});
data.push(targetRow);
});
this._networkShareTargetDatabaseNamesTable.dataValues = data;
data = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
targetRow.push({
value: db
});
targetRow.push({
value: this._blobContainerTargetDatabaseNames[index]
});
targetRow.push({
value: this._blobContainerResourceGroupDropdowns[index]
});
targetRow.push({
value: this._blobContainerStorageAccountDropdowns[index]
});
targetRow.push({
value: this._blobContainerDropdowns[index]
});
targetRow.push({
value: this._blobContainerLastBackupFileDropdowns[index]
});
data.push(targetRow);
});
await this._blobContainerTargetDatabaseNamesTable.setDataValues(data);
await this.getSubscriptionValues();
this.migrationStateModel.refreshDatabaseBackupPage = false;
} catch (error) {
console.log(error);
let errorText = error?.message;
if (errorText === constants.INVALID_OWNER_URI) {
errorText = constants.DATABASE_BACKUP_PAGE_LOAD_ERROR;
} }
}); this.wizard.message = {
text: errorText,
description: error?.stack,
let data: azdata.DeclarativeTableCellValue[][] = []; level: azdata.window.MessageLevel.Error
this.migrationStateModel._migrationDbs.forEach((db, index) => { };
const targetRow: azdata.DeclarativeTableCellValue[] = []; }
targetRow.push({
value: db
});
targetRow.push({
value: this._networkShareTargetDatabaseNames[index]
});
data.push(targetRow);
});
this._networkShareTargetDatabaseNamesTable.dataValues = data;
data = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
targetRow.push({
value: db
});
targetRow.push({
value: this._blobContainerTargetDatabaseNames[index]
});
targetRow.push({
value: this._blobContainerResourceGroupDropdowns[index]
});
targetRow.push({
value: this._blobContainerStorageAccountDropdowns[index]
});
targetRow.push({
value: this._blobContainerDropdowns[index]
});
targetRow.push({
value: this._blobContainerLastBackupFileDropdowns[index]
});
data.push(targetRow);
});
await this._blobContainerTargetDatabaseNamesTable.setDataValues(data);
await this.getSubscriptionValues();
this.migrationStateModel.refreshDatabaseBackupPage = false;
} }
this.wizard.registerNavigationValidator((pageChangeInfo) => { this.wizard.registerNavigationValidator((pageChangeInfo) => {
@@ -1131,7 +1142,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none'; this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none';
//Preserving the database Names between the 2 tables. //Preserving the database Names between the 2 tables.
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames; this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames;
} }
@@ -1150,7 +1161,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
await this.validateFields(); await this.validateFields();
} }
private async validateFields(): Promise<void> { private async validateFields(): Promise<void> {
await this._sqlSourceUsernameInput.validate(); await this._sqlSourceUsernameInput.validate();
await this._sqlSourcePassword.validate(); await this._sqlSourcePassword.validate();
@@ -1175,7 +1185,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
} }
private async getSubscriptionValues(): Promise<void> { private async getSubscriptionValues(): Promise<void> {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
this.migrationStateModel._targetSubscription = <azureResource.AzureResourceSubscription>this.migrationStateModel.savedInfo.targetSubscription; this.migrationStateModel._targetSubscription = <azureResource.AzureResourceSubscription>this.migrationStateModel.savedInfo.targetSubscription;
this.migrationStateModel._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance; this.migrationStateModel._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance;
} }
@@ -1196,7 +1206,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._networkShareStorageAccountResourceGroupDropdown.loading = true; this._networkShareStorageAccountResourceGroupDropdown.loading = true;
try { try {
this._networkShareStorageAccountResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription); this._networkShareStorageAccountResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup && this._networkShareStorageAccountResourceGroupDropdown.values) { if (this.hasSavedInfo(NetworkContainerType.NETWORK_SHARE, this._networkShareStorageAccountResourceGroupDropdown.values)) {
this._networkShareStorageAccountResourceGroupDropdown.values.forEach((resource, index) => { this._networkShareStorageAccountResourceGroupDropdown.values.forEach((resource, index) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.networkShare?.resourceGroup?.id?.toLowerCase()) { if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.networkShare?.resourceGroup?.id?.toLowerCase()) {
selectDropDownIndex(this._networkShareStorageAccountResourceGroupDropdown, index); selectDropDownIndex(this._networkShareStorageAccountResourceGroupDropdown, index);
@@ -1231,7 +1241,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const resourceGroupValues = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription); const resourceGroupValues = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
this._blobContainerResourceGroupDropdowns.forEach((dropDown, index) => { this._blobContainerResourceGroupDropdowns.forEach((dropDown, index) => {
dropDown.values = resourceGroupValues; dropDown.values = resourceGroupValues;
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup && dropDown.values) { if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, dropDown.values)) {
dropDown.values.forEach((resource, resourceIndex) => { dropDown.values.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.resourceGroup?.id?.toLowerCase()) { if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.resourceGroup?.id?.toLowerCase()) {
selectDropDownIndex(dropDown, resourceIndex); selectDropDownIndex(dropDown, resourceIndex);
@@ -1251,8 +1261,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadBlobStorageDropdown(index: number): Promise<void> { private async loadBlobStorageDropdown(index: number): Promise<void> {
this._blobContainerStorageAccountDropdowns[index].loading = true; this._blobContainerStorageAccountDropdowns[index].loading = true;
try { try {
this._blobContainerStorageAccountDropdowns[index].values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].resourceGroup); this._blobContainerStorageAccountDropdowns[index].values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.resourceGroup);
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup && this._blobContainerStorageAccountDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index].storageAccount) { if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerStorageAccountDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.storageAccount)) {
this._blobContainerStorageAccountDropdowns[index].values!.forEach((resource, resourceIndex) => { this._blobContainerStorageAccountDropdowns[index].values!.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.storageAccount?.id?.toLowerCase()) { if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.storageAccount?.id?.toLowerCase()) {
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[index], resourceIndex); selectDropDownIndex(this._blobContainerStorageAccountDropdowns[index], resourceIndex);
@@ -1271,9 +1281,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadBlobContainerDropdown(index: number): Promise<void> { private async loadBlobContainerDropdown(index: number): Promise<void> {
this._blobContainerDropdowns[index].loading = true; this._blobContainerDropdowns[index].loading = true;
try { try {
const blobContainerValues = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].storageAccount); const blobContainerValues = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount);
this._blobContainerDropdowns[index].values = blobContainerValues; this._blobContainerDropdowns[index].values = blobContainerValues;
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup && this._blobContainerDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index].blobContainer) { if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.blobContainer)) {
this._blobContainerDropdowns[index].values!.forEach((resource, resourceIndex) => { this._blobContainerDropdowns[index].values!.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.blobContainer?.id?.toLowerCase()) { if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.blobContainer?.id?.toLowerCase()) {
selectDropDownIndex(this._blobContainerDropdowns[index], resourceIndex); selectDropDownIndex(this._blobContainerDropdowns[index], resourceIndex);
@@ -1292,9 +1302,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadBlobLastBackupFileDropdown(index: number): Promise<void> { private async loadBlobLastBackupFileDropdown(index: number): Promise<void> {
this._blobContainerLastBackupFileDropdowns[index].loading = true; this._blobContainerLastBackupFileDropdowns[index].loading = true;
try { try {
const blobLastBackupFileValues = await this.migrationStateModel.getBlobLastBackupFileNameValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index].storageAccount, this.migrationStateModel._databaseBackup.blobs[index].blobContainer); const blobLastBackupFileValues = await this.migrationStateModel.getBlobLastBackupFileNameValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount, this.migrationStateModel._databaseBackup.blobs[index]?.blobContainer);
this._blobContainerLastBackupFileDropdowns[index].values = blobLastBackupFileValues; this._blobContainerLastBackupFileDropdowns[index].values = blobLastBackupFileValues;
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup && this._blobContainerLastBackupFileDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index].lastBackupFile) { if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerLastBackupFileDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.lastBackupFile)) {
this._blobContainerLastBackupFileDropdowns[index].values!.forEach((resource, resourceIndex) => { this._blobContainerLastBackupFileDropdowns[index].values!.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.lastBackupFile!.toLowerCase()) { if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.lastBackupFile!.toLowerCase()) {
selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], resourceIndex); selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], resourceIndex);
@@ -1334,4 +1344,12 @@ export class DatabaseBackupPage extends MigrationWizardPage {
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[rowIndex], 0); selectDropDownIndex(this._blobContainerStorageAccountDropdowns[rowIndex], 0);
await this._blobContainerStorageAccountDropdowns[rowIndex].updateProperties(dropdownProps); await this._blobContainerStorageAccountDropdowns[rowIndex].updateProperties(dropdownProps);
} }
private hasSavedInfo(networkContainerType: NetworkContainerType, values: any): boolean {
if (this.migrationStateModel._databaseBackup.networkContainerType === networkContainerType &&
(this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) && values)) {
return true;
}
return false;
}
} }

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; import { MigrationStateModel, Page, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import { IconPath, IconPathHelper } from '../constants/iconPathHelper'; import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
import { debounce } from '../api/utils'; import { debounce } from '../api/utils';
@@ -275,17 +275,26 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
} }
).component(); ).component();
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 1) { if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseSelector) {
await this._databaseSelectorTable.setDataValues(this.migrationStateModel.savedInfo.selectedDatabases); await this._databaseSelectorTable.setDataValues(this.migrationStateModel.savedInfo.selectedDatabases);
} else { } else {
if (this.migrationStateModel.retryMigration) {
const sourceDatabaseName = this.migrationStateModel.savedInfo.databaseList[0];
this._databaseTableValues.forEach((row, index) => {
const dbName = row[1].value as string;
if (dbName?.toLowerCase() === sourceDatabaseName?.toLowerCase()) {
row[0].value = true;
} else {
row[0].enabled = false;
}
});
}
await this._databaseSelectorTable.setDataValues(this._databaseTableValues); await this._databaseSelectorTable.setDataValues(this._databaseTableValues);
await this.updateValuesOnSelection();
} }
this._disposables.push(this._databaseSelectorTable.onDataChanged(async () => { this._disposables.push(this._databaseSelectorTable.onDataChanged(async () => {
await this._dbCount.updateProperties({ await this.updateValuesOnSelection();
'value': constants.DATABASES_SELECTED(this.selectedDbs().length, this._databaseTableValues.length)
});
this.migrationStateModel._databaseAssessment = this.selectedDbs();
this.migrationStateModel.databaseSelectorTableValues = <azdata.DeclarativeTableCellValue[][]>this._databaseSelectorTable.dataValues;
})); }));
const flex = view.modelBuilder.flexContainer().withLayout({ const flex = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column', flexFlow: 'column',
@@ -314,6 +323,14 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
return result; return result;
} }
private async updateValuesOnSelection() {
await this._dbCount.updateProperties({
'value': constants.DATABASES_SELECTED(this.selectedDbs().length, this._databaseTableValues.length)
});
this.migrationStateModel._databaseAssessment = this.selectedDbs();
this.migrationStateModel.databaseSelectorTableValues = <azdata.DeclarativeTableCellValue[][]>this._databaseSelectorTable.dataValues;
}
// undo when bug #16445 is fixed // undo when bug #16445 is fixed
private createIconTextCell(icon: IconPath, text: string): string { private createIconTextCell(icon: IconPath, text: string): string {
return text; return text;

View File

@@ -86,7 +86,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
} }
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> { public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime)) {
this.migrationStateModel._targetSubscription = <azureResource.AzureResourceSubscription>this.migrationStateModel.savedInfo.targetSubscription; this.migrationStateModel._targetSubscription = <azureResource.AzureResourceSubscription>this.migrationStateModel.savedInfo.targetSubscription;
this.migrationStateModel._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance; this.migrationStateModel._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance;
} }
@@ -391,7 +391,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this._resourceGroupDropdown.loading = true; this._resourceGroupDropdown.loading = true;
try { try {
this._resourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription); this._resourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription);
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._resourceGroupDropdown.values) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._resourceGroupDropdown.values)) {
this._resourceGroupDropdown.values.forEach((resource, resourceIndex) => { this._resourceGroupDropdown.values.forEach((resource, resourceIndex) => {
const resourceId = this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase(); const resourceId = this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase();
if (resourceId && (<azdata.CategoryValue>resource).name.toLowerCase() === getFullResourceGroupFromId(resourceId)) { if (resourceId && (<azdata.CategoryValue>resource).name.toLowerCase() === getFullResourceGroupFromId(resourceId)) {
@@ -409,8 +409,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
try { try {
this._dmsDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, <SqlManagedInstance>this.migrationStateModel._targetServerInstance, resourceGroupName); this._dmsDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, <SqlManagedInstance>this.migrationStateModel._targetServerInstance, resourceGroupName);
const selectedSqlMigrationService = this._dmsDropdown.values.find(v => v.displayName.toLowerCase() === this.migrationStateModel._sqlMigrationService?.name?.toLowerCase()); const selectedSqlMigrationService = this._dmsDropdown.values.find(v => v.displayName.toLowerCase() === this.migrationStateModel._sqlMigrationService?.name?.toLowerCase());
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._dmsDropdown.values)) {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._dmsDropdown.values) {
this._dmsDropdown.values.forEach((resource, resourceIndex) => { this._dmsDropdown.values.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase()) { if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase()) {
selectDropDownIndex(this._dmsDropdown, resourceIndex); selectDropDownIndex(this._dmsDropdown, resourceIndex);

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationMode, MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; import { MigrationMode, MigrationStateModel, Page, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import * as styles from '../constants/styles'; import * as styles from '../constants/styles';
@@ -113,7 +113,7 @@ export class MigrationModePage extends MigrationWizardPage {
} }
}).component(); }).component();
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 3) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode)) {
if (this.migrationStateModel.savedInfo.migrationMode === MigrationMode.ONLINE) { if (this.migrationStateModel.savedInfo.migrationMode === MigrationMode.ONLINE) {
onlineButton.checked = true; onlineButton.checked = true;
offlineButton.checked = false; offlineButton.checked = false;

View File

@@ -197,6 +197,14 @@ export class SKURecommendationPage extends MigrationWizardPage {
})); }));
await this._view.initializeModel(this._rootContainer); await this._view.initializeModel(this._rootContainer);
if (this.hasSavedInfo()) {
if (this.migrationStateModel.savedInfo.migrationTargetType === MigrationTargetType.SQLMI) {
this.migrationStateModel._miDbs = this.migrationStateModel.savedInfo.databaseList;
} else {
this.migrationStateModel._vmDbs = this.migrationStateModel.savedInfo.databaseList;
}
}
} }
private createStatusComponent(view: azdata.ModelView): azdata.TextComponent { private createStatusComponent(view: azdata.ModelView): azdata.TextComponent {
@@ -300,7 +308,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
}).component(); }).component();
let serverName = ''; let serverName = '';
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.serverName) { if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.serverName)) {
serverName = this.migrationStateModel.serverName; serverName = this.migrationStateModel.serverName;
} else { } else {
serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName; serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
@@ -505,7 +513,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
this.migrationStateModel._migrationDbs = miDbs; this.migrationStateModel._migrationDbs = miDbs;
} else { } else {
this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM; this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM;
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) { if ((this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation)) {
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel.savedInfo.databaseList.length, this.migrationStateModel._databaseAssessment.length); this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel.savedInfo.databaseList.length, this.migrationStateModel._databaseAssessment.length);
} else { } else {
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(vmDbs.length, this.migrationStateModel._databaseAssessment.length); this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(vmDbs.length, this.migrationStateModel._databaseAssessment.length);
@@ -540,12 +548,12 @@ export class SKURecommendationPage extends MigrationWizardPage {
await this.migrationStateModel.getDatabaseAssessments(MigrationTargetType.SQLMI); await this.migrationStateModel.getDatabaseAssessments(MigrationTargetType.SQLMI);
} }
const assessmentError = this.migrationStateModel._assessmentResults.assessmentError; const assessmentError = this.migrationStateModel._assessmentResults?.assessmentError;
if (assessmentError) { if (assessmentError) {
errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`); errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`);
} }
if (this.migrationStateModel?._assessmentResults?.errors?.length! > 0) { if (this.migrationStateModel?._assessmentResults?.errors?.length! > 0) {
errors.push(...this.migrationStateModel._assessmentResults.errors?.map( errors.push(...this.migrationStateModel._assessmentResults?.errors?.map(
e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!); e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!);
} }
@@ -566,11 +574,11 @@ export class SKURecommendationPage extends MigrationWizardPage {
} else { } else {
this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration; this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration;
this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName); this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName);
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults.databaseAssessments.length); this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults?.databaseAssessments?.length);
} }
} }
if ((this.migrationStateModel.resumeAssessment) && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) { if (this.hasSavedInfo()) {
if (this.migrationStateModel.savedInfo.migrationTargetType) { if (this.migrationStateModel.savedInfo.migrationTargetType) {
this._rbg.selectedCardId = this.migrationStateModel.savedInfo.migrationTargetType; this._rbg.selectedCardId = this.migrationStateModel.savedInfo.migrationTargetType;
await this.refreshCardText(); await this.refreshCardText();
@@ -602,9 +610,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
await this.assessmentGroupContainer.updateCssStyles({ 'display': display }); await this.assessmentGroupContainer.updateCssStyles({ 'display': display });
this.assessmentGroupContainer.display = display; this.assessmentGroupContainer.display = display;
display = this._rbg.selectedCardId display = (this._rbg.selectedCardId
&& (!failedAssessment || this._skipAssessmentCheckbox.checked) && (!failedAssessment || this._skipAssessmentCheckbox.checked)
&& this.migrationStateModel._migrationDbs.length > 0 && this.migrationStateModel._migrationDbs.length > 0)
? 'inline' ? 'inline'
: 'none'; : 'none';
await this._targetContainer.updateCssStyles({ 'display': display }); await this._targetContainer.updateCssStyles({ 'display': display });
@@ -614,7 +622,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
} }
private async populateSubscriptionDropdown(): Promise<void> { private async populateSubscriptionDropdown(): Promise<void> {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) { if (this.hasSavedInfo()) {
this.migrationStateModel._azureAccount = <azdata.Account>this.migrationStateModel.savedInfo.azureAccount; this.migrationStateModel._azureAccount = <azdata.Account>this.migrationStateModel.savedInfo.azureAccount;
} }
if (!this.migrationStateModel._targetSubscription) { if (!this.migrationStateModel._targetSubscription) {
@@ -628,9 +636,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._managedInstanceSubscriptionDropdown.loading = false; this._managedInstanceSubscriptionDropdown.loading = false;
this._resourceDropdown.loading = false; this._resourceDropdown.loading = false;
} }
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 2 && this._managedInstanceSubscriptionDropdown.values) { if (this.hasSavedInfo() && this._managedInstanceSubscriptionDropdown.values) {
this._managedInstanceSubscriptionDropdown.values.forEach((subscription, index) => { this._managedInstanceSubscriptionDropdown.values!.forEach((subscription, index) => {
if ((<azdata.CategoryValue>subscription).name === this.migrationStateModel.savedInfo?.subscription?.id) { if ((<azdata.CategoryValue>subscription).name.toLowerCase() === this.migrationStateModel.savedInfo?.subscription?.id.toLowerCase()) {
selectDropDownIndex(this._managedInstanceSubscriptionDropdown, index); selectDropDownIndex(this._managedInstanceSubscriptionDropdown, index);
} }
}); });
@@ -645,9 +653,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._azureLocationDropdown.loading = true; this._azureLocationDropdown.loading = true;
try { try {
this._azureResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription); this._azureResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription);
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 2 && this._azureResourceGroupDropdown.values) { if (this.hasSavedInfo() && this._azureResourceGroupDropdown.values) {
this._azureResourceGroupDropdown.values.forEach((resourceGroup, index) => { this._azureResourceGroupDropdown.values.forEach((resourceGroup, index) => {
if (resourceGroup.name === this.migrationStateModel.savedInfo?.resourceGroup?.id) { if (resourceGroup.name.toLowerCase() === this.migrationStateModel.savedInfo?.resourceGroup?.id.toLowerCase()) {
selectDropDownIndex(this._azureResourceGroupDropdown, index); selectDropDownIndex(this._azureResourceGroupDropdown, index);
} }
}); });
@@ -655,7 +663,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
selectDropDownIndex(this._azureResourceGroupDropdown, 0); selectDropDownIndex(this._azureResourceGroupDropdown, 0);
} }
this._azureLocationDropdown.values = await this.migrationStateModel.getAzureLocationDropdownValues(this.migrationStateModel._targetSubscription); this._azureLocationDropdown.values = await this.migrationStateModel.getAzureLocationDropdownValues(this.migrationStateModel._targetSubscription);
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 2 && this._azureLocationDropdown.values) { if (this.hasSavedInfo() && this._azureLocationDropdown.values) {
this._azureLocationDropdown.values.forEach((location, index) => { this._azureLocationDropdown.values.forEach((location, index) => {
if (location.displayName === this.migrationStateModel.savedInfo?.location?.displayName) { if (location.displayName === this.migrationStateModel.savedInfo?.location?.displayName) {
selectDropDownIndex(this._azureLocationDropdown, index); selectDropDownIndex(this._azureLocationDropdown, index);
@@ -690,9 +698,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
this.migrationStateModel._location, this.migrationStateModel._location,
this.migrationStateModel._resourceGroup); this.migrationStateModel._resourceGroup);
} }
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 2 && this._resourceDropdown.values) { if (this.hasSavedInfo() && this._resourceDropdown.values) {
this._resourceDropdown.values.forEach((resource, index) => { this._resourceDropdown.values.forEach((resource, index) => {
if (resource.displayName === this.migrationStateModel.savedInfo?.targetServerInstance?.name) { if (resource.displayName.toLowerCase() === this.migrationStateModel.savedInfo?.targetServerInstance?.name.toLowerCase()) {
selectDropDownIndex(this._resourceDropdown, index); selectDropDownIndex(this._resourceDropdown, index);
} }
}); });
@@ -708,9 +716,6 @@ export class SKURecommendationPage extends MigrationWizardPage {
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> { public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator((pageChangeInfo) => { this.wizard.registerNavigationValidator((pageChangeInfo) => {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) {
this.migrationStateModel._migrationDbs = this.migrationStateModel.savedInfo.databaseList;
}
const errors: string[] = []; const errors: string[] = [];
this.wizard.message = { this.wizard.message = {
text: '', text: '',
@@ -785,8 +790,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._targetContainer.display = (this.migrationStateModel._migrationDbs.length === 0) ? 'none' : 'inline'; this._targetContainer.display = (this.migrationStateModel._migrationDbs.length === 0) ? 'none' : 'inline';
if (this.migrationStateModel._assessmentResults) { if (this.migrationStateModel._assessmentResults) {
const dbCount = this.migrationStateModel._assessmentResults.databaseAssessments?.length; const dbCount = this.migrationStateModel._assessmentResults?.databaseAssessments?.length;
const dbWithoutIssuesCount = this.migrationStateModel._assessmentResults.databaseAssessments?.filter(db => db.issues?.length === 0).length; const dbWithoutIssuesCount = this.migrationStateModel._assessmentResults?.databaseAssessments?.filter(db => db.issues?.length === 0).length;
this._rbg.cards[0].descriptions[1].textValue = constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount); this._rbg.cards[0].descriptions[1].textValue = constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount);
this._rbg.cards[1].descriptions[1].textValue = constants.CAN_BE_MIGRATED(dbCount, dbCount); this._rbg.cards[1].descriptions[1].textValue = constants.CAN_BE_MIGRATED(dbCount, dbCount);
@@ -837,6 +842,10 @@ export class SKURecommendationPage extends MigrationWizardPage {
}).component(); }).component();
return this._assessmentInfo; return this._assessmentInfo;
} }
private hasSavedInfo(): boolean {
return this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation);
}
} }

View File

@@ -112,7 +112,7 @@ export class SummaryPage extends MigrationWizardPage {
] ]
); );
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE && this.migrationStateModel._nodeNames.length > 0) { if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE && this.migrationStateModel._nodeNames?.length > 0) {
this._flexContainer.addItem(createInformationRow(this._view, constants.SHIR, this.migrationStateModel._nodeNames.join(', '))); this._flexContainer.addItem(createInformationRow(this._view, constants.SHIR, this.migrationStateModel._nodeNames.join(', ')));
} }
} }

View File

@@ -64,7 +64,7 @@ export class WizardController {
const wizardSetupPromises: Thenable<void>[] = []; const wizardSetupPromises: Thenable<void>[] = [];
wizardSetupPromises.push(...pages.map(p => p.registerWizardContent())); wizardSetupPromises.push(...pages.map(p => p.registerWizardContent()));
wizardSetupPromises.push(this._wizardObject.open()); wizardSetupPromises.push(this._wizardObject.open());
if (this._model.resumeAssessment) { if (this._model.retryMigration || this._model.resumeAssessment) {
if (this._model.savedInfo.closedPage >= Page.MigrationMode) { if (this._model.savedInfo.closedPage >= Page.MigrationMode) {
this._model.refreshDatabaseBackupPage = true; this._model.refreshDatabaseBackupPage = true;
} }