Retry sql migration (#17376) (#17448)

This commit is contained in:
brian-harris
2021-10-21 21:14:15 -07:00
committed by GitHub
parent b9a7d5e4bd
commit c5a27a89f3
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",
"displayName": "%displayName%",
"description": "%description%",
"version": "0.1.8",
"version": "0.1.9",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
@@ -81,6 +81,11 @@
"command": "sqlmigration.cancel.migration",
"title": "%cancel-migration-menu%",
"category": "%migration-context-menu-category%"
},
{
"command": "sqlmigration.retry.migration",
"title": "%retry-migration-menu%",
"category": "%migration-context-menu-category%"
}
],
"menu": {
@@ -116,6 +121,10 @@
{
"command": "sqlmigration.cancel.migration",
"when": "false"
},
{
"command": "sqlmigration.retry.migration",
"when": "false"
}
]
},
@@ -174,4 +183,4 @@
"devDependencies": {
"@types/uuid": "^8.3.1"
}
}
}

View File

@@ -14,5 +14,6 @@
"view-target-menu": "Azure SQL Target details",
"view-service-menu": "Database Migration Service 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();
}
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 {
name: string;
subscriptionId: string;

View File

@@ -3,7 +3,8 @@
* 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';
export enum SQLTargetAssetType {
@@ -22,6 +23,28 @@ export function getMigrationTargetType(migration: MigrationContext): string {
}
}
export function getMigrationMode(migration: MigrationContext): string {
return migration.migrationContext.properties.offlineConfiguration?.offline?.valueOf() ? loc.OFFLINE : loc.OFFLINE;
export function getMigrationTargetTypeEnum(migration: MigrationContext): MigrationTargetType | undefined {
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 emptyTable: IconPath;
public static addAzureAccount: IconPath;
public static retry: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) {
IconPathHelper.copy = {
@@ -153,5 +154,9 @@ export class IconPathHelper {
light: 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');
// Saved Assessment Dialog
export const NEXT_LABEL = localize('sql.migration.saved.assessment.next', "Next");
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");
// 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 {
private _context: vscode.ExtensionContext;
private _migrationStatusCardsContainer!: azdata.FlexContainer;
private _migrationStatusCardLoadingContainer!: azdata.LoadingComponent;
@@ -52,7 +53,8 @@ export class DashboardWidget {
private isRefreshing: boolean = false;
constructor() {
constructor(context: vscode.ExtensionContext) {
this._context = context;
}
private async getCurrentMigrations(): Promise<MigrationContext[]> {
@@ -470,7 +472,7 @@ export class DashboardWidget {
this._disposables.push(this._viewAllMigrationsButton.onDidClick(async (e) => {
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({
@@ -596,7 +598,7 @@ export class DashboardWidget {
loc.MIGRATION_IN_PROGRESS
);
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();
}));
@@ -610,7 +612,7 @@ export class DashboardWidget {
true
);
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();
}));
@@ -623,7 +625,7 @@ export class DashboardWidget {
loc.MIGRATION_COMPLETED
);
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();
}));
this._migrationStatusCardsContainer.addItem(
@@ -636,7 +638,7 @@ export class DashboardWidget {
loc.MIGRATION_CUTOVER_CARD
);
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();
}));
this._migrationStatusCardsContainer.addItem(
@@ -648,7 +650,7 @@ export class DashboardWidget {
loc.MIGRATION_FAILED
);
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();
}));
this._migrationStatusCardsContainer.addItem(

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
import { MigrationStateModel, MigrationTargetType, Page } from '../../models/stateMachine';
import { SqlDatabaseTree } from './sqlDatabasesTree';
import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
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) {
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._tree = new SqlDatabaseTree(this._model, this._targetType);

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
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 { debounce } from '../../api/utils';
import { IconPath, IconPathHelper } from '../../constants/iconPathHelper';
@@ -142,7 +142,7 @@ export class SqlDatabaseTree {
...styles.BOLD_NOTE_CSS,
'margin': '0px 15px 0px 15px'
},
value: constants.DATABASES(0, this._model._databaseAssessment.length)
value: constants.DATABASES(0, this._model._databaseAssessment?.length)
}).component();
return this._databaseCount;
}
@@ -187,10 +187,7 @@ export class SqlDatabaseTree {
).component();
this._disposables.push(this._databaseTable.onDataChanged(async () => {
await this._databaseCount.updateProperties({
'value': constants.DATABASES(this.selectedDbs().length, this._model._databaseAssessment.length)
});
this._model._databaseSelection = <azdata.DeclarativeTableCellValue[][]>this._databaseTable.dataValues;
await this.updateValuesOnSelection();
}));
this._disposables.push(this._databaseTable.onRowSelected(async (e) => {
@@ -200,7 +197,7 @@ export class SqlDatabaseTree {
this._activeIssues = [];
}
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;
await this._resultComponent.updateCssStyles({
'display': 'block'
@@ -307,7 +304,7 @@ export class SqlDatabaseTree {
'display': 'none'
});
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) {
await this.refreshResults();
}
@@ -424,7 +421,7 @@ export class SqlDatabaseTree {
}
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;
if (failedAssessment) {
this._dialog.message = {
@@ -439,12 +436,12 @@ export class SqlDatabaseTree {
private getAssessmentError(): string {
const errors: string[] = [];
const assessmentError = this._model._assessmentResults.assessmentError;
const assessmentError = this._model._assessmentResults?.assessmentError;
if (assessmentError) {
errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`);
}
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}`)!);
}
@@ -791,7 +788,7 @@ export class SqlDatabaseTree {
public async refreshResults(): Promise<void> {
if (this._targetType === MigrationTargetType.SQLMI) {
if (this._activeIssues.length === 0) {
if (this._activeIssues?.length === 0) {
/// show no issues here
await this._assessmentsTable.updateCssStyles({
'display': 'none',
@@ -858,7 +855,7 @@ export class SqlDatabaseTree {
|| [];
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> {
@@ -872,7 +869,7 @@ export class SqlDatabaseTree {
await this._impactedObjectsTable.setDataValues(this._impactedObjects.map(
(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 {
@@ -927,17 +924,17 @@ export class SqlDatabaseTree {
style: styleLeft
},
{
value: this._model._assessmentResults.issues.length,
value: this._model._assessmentResults?.issues?.length,
style: styleRight
}
]
];
this._model._assessmentResults.databaseAssessments.sort((db1, db2) => {
return db2.issues.length - db1.issues.length;
this._model._assessmentResults?.databaseAssessments.sort((db1, db2) => {
return db2.issues?.length - db1.issues?.length;
});
// Reset the dbName list so that it is in sync with the table
this._dbNames = this._model._assessmentResults.databaseAssessments.map(da => da.name);
this._model._assessmentResults.databaseAssessments.forEach((db) => {
this._dbNames = this._model._assessmentResults?.databaseAssessments.map(da => da.name);
this._model._assessmentResults?.databaseAssessments.forEach((db) => {
let selectable = true;
if (db.issues.find(item => item.databaseRestoreFails)) {
selectable = false;
@@ -954,7 +951,7 @@ export class SqlDatabaseTree {
style: styleLeft
},
{
value: db.issues.length,
value: db.issues?.length,
style: styleRight
}
]
@@ -962,13 +959,27 @@ export class SqlDatabaseTree {
});
}
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);
} 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.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
private createIconTextCell(icon: IconPath, text: string): string {
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 { EOL } from 'os';
import { ConfirmCutoverDialog } from './confirmCutoverDialog';
import { RetryMigrationDialog } from '../retryMigration/retryMigrationDialog';
import * as styles from '../../constants/styles';
import { canRetryMigration } from '../../constants/helper';
const refreshFrequency: SupportedAutoRefreshIntervals = 30000;
const statusImageSize: number = 14;
export class MigrationCutoverDialog {
private _context: vscode.ExtensionContext;
private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView;
private _model: MigrationCutoverDialogModel;
private _migration: MigrationContext;
private _databaseTitleName!: azdata.TextComponent;
private _cutoverButton!: azdata.ButtonComponent;
@@ -29,6 +33,7 @@ export class MigrationCutoverDialog {
private _refreshLoader!: azdata.LoadingComponent;
private _copyDatabaseMigrationDetails!: azdata.ButtonComponent;
private _newSupportRequest!: azdata.ButtonComponent;
private _retryButton!: azdata.ButtonComponent;
private _sourceDatabaseInfoField!: InfoFieldSchema;
private _sourceDetailsInfoField!: InfoFieldSchema;
@@ -53,7 +58,9 @@ export class MigrationCutoverDialog {
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._dialogObject = azdata.window.createModelViewDialog('', 'MigrationCutoverDialog', 'wide');
}
@@ -301,11 +308,11 @@ export class MigrationCutoverDialog {
iconWidth: '16px',
label: loc.COMPLETE_CUTOVER,
height: '20px',
width: '150px',
width: '140px',
enabled: false,
CSSStyles: {
...styles.BODY_CSS,
'display': this._isOnlineMigration() ? 'inline' : 'none'
'display': this._isOnlineMigration() ? 'block' : 'none'
}
}).component();
@@ -330,7 +337,7 @@ export class MigrationCutoverDialog {
iconWidth: '16px',
label: loc.CANCEL_MIGRATION,
height: '20px',
width: '150px',
width: '140px',
enabled: false,
CSSStyles: {
...styles.BODY_CSS,
@@ -353,6 +360,28 @@ export class MigrationCutoverDialog {
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({
iconPath: IconPathHelper.refresh,
@@ -360,7 +389,7 @@ export class MigrationCutoverDialog {
iconWidth: '16px',
label: 'Refresh',
height: '20px',
width: '100px',
width: '80px',
CSSStyles: {
...styles.BODY_CSS,
}
@@ -379,7 +408,7 @@ export class MigrationCutoverDialog {
iconWidth: '16px',
label: loc.COPY_MIGRATION_DETAILS,
height: '20px',
width: '200px',
width: '160px',
CSSStyles: {
...styles.BODY_CSS,
}
@@ -406,7 +435,7 @@ export class MigrationCutoverDialog {
iconHeight: '16px',
iconWidth: '16px',
height: '20px',
width: '180px',
width: '160px',
CSSStyles: {
...styles.BODY_CSS,
}
@@ -567,7 +596,7 @@ export class MigrationCutoverDialog {
if (this._isOnlineMigration()) {
await this._cutoverButton.updateCssStyles({
'display': 'inline'
'display': 'block'
});
}
@@ -720,6 +749,9 @@ export class MigrationCutoverDialog {
this._cancelButton.enabled =
migrationStatusTextValue === MigrationStatus.Creating ||
migrationStatusTextValue === MigrationStatus.InProgress;
this._retryButton.enabled = canRetryMigration(migrationStatusTextValue);
} catch (e) {
displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_STATUS_REFRESH_ERROR, e);
console.log(e);

View File

@@ -14,7 +14,8 @@ import { clearDialogMessage, convertTimeDifferenceToDuration, displayDialogError
import { SqlMigrationServiceDetailsDialog } from '../sqlMigrationService/sqlMigrationServiceDetailsDialog';
import { ConfirmCutoverDialog } from '../migrationCutover/confirmCutoverDialog';
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;
@@ -29,9 +30,11 @@ const MenuCommands = {
ViewService: 'sqlmigration.view.service',
CopyMigration: 'sqlmigration.copy.migration',
CancelMigration: 'sqlmigration.cancel.migration',
RetryMigration: 'sqlmigration.retry.migration',
};
export class MigrationStatusDialog {
private _context: vscode.ExtensionContext;
private _model: MigrationStatusDialogModel;
private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView;
@@ -45,7 +48,8 @@ export class MigrationStatusDialog {
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._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide');
}
@@ -221,7 +225,7 @@ export class MigrationStatusDialog {
async (migrationId: string) => {
try {
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();
} catch (e) {
console.log(e);
@@ -302,6 +306,25 @@ export class MigrationStatusDialog {
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> {
@@ -366,7 +389,7 @@ export class MigrationStatusDialog {
}).component();
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
.flexContainer()
@@ -416,6 +439,10 @@ export class MigrationStatusDialog {
menuCommands.push(MenuCommands.CancelMigration);
}
if (canRetryMigration(migrationStatus)) {
menuCommands.push(MenuCommands.RetryMigration);
}
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);
}
}
}
}
private checkSavedInfo(serverName: string): SavedInfo | undefined {
@@ -138,7 +134,7 @@ let sqlMigration: SQLMigration;
export async function activate(context: vscode.ExtensionContext) {
sqlMigration = new SQLMigration(context);
await sqlMigration.registerCommands();
let widget = new DashboardWidget();
let widget = new DashboardWidget(context);
widget.register();
}

View File

@@ -71,6 +71,7 @@ export enum Page {
export enum WizardEntryPoint {
Default = 'Default',
SaveAndClose = 'SaveAndClose',
RetryMigration = 'RetryMigration',
}
export interface DatabaseBackupModel {
@@ -188,6 +189,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public refreshDatabaseBackupPage!: boolean;
public _databaseSelection!: azdata.DeclarativeTableCellValue[][];
public retryMigration!: boolean;
public resumeAssessment!: boolean;
public savedInfo!: SavedInfo;
public closedPage!: number;
@@ -293,7 +295,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
private async generateAssessmentTelemetry(): Promise<void> {
try {
let serverIssues = this._assessmentResults.issues.map(i => {
let serverIssues = this._assessmentResults?.issues.map(i => {
return {
ruleId: i.ruleId,
count: i.impactedObjects.length
@@ -337,10 +339,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
'serverErrors': JSON.stringify(serverErrors),
},
{
'issuesCount': this._assessmentResults.issues.length,
'warningsCount': this._assessmentResults.databaseAssessments.reduce((count, d) => count + d.issues.length, 0),
'issuesCount': this._assessmentResults?.issues.length,
'warningsCount': this._assessmentResults?.databaseAssessments.reduce((count, d) => count + d.issues.length, 0),
'durationInMilliseconds': endTime.getTime() - startTime.getTime(),
'databaseCount': this._assessmentResults.databaseAssessments.length,
'databaseCount': this._assessmentResults?.databaseAssessments.length,
'serverHostCpuCount': this._assessmentApiResponse?.assessmentResult?.cpuCoreCount,
'serverHostPhysicalMemoryInBytes': this._assessmentApiResponse?.assessmentResult?.physicalServerMemory,
'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[]> {
let managedInstanceValues: azdata.CategoryValue[] = [];
if (!this._azureAccount) {
if (!this._azureAccount || !subscription) {
return managedInstanceValues;
}
try {
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 false;
@@ -678,7 +680,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
try {
if (this._azureAccount && subscription && resourceGroup) {
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) {
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;
if (this.resumeAssessment) {
wizardEntryPoint = WizardEntryPoint.SaveAndClose;
} else if (this.retryMigration) {
wizardEntryPoint = WizardEntryPoint.RetryMigration;
}
if (response.status === 201 || response.status === 200) {
sendSqlMigrationActionEvent(

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
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 { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import { deepClone, findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
@@ -111,9 +111,9 @@ export class AccountsSelectionPage extends MigrationWizardPage {
await this._accountTenantFlexContainer.updateCssStyles({
'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) => {
if (account.name === this.migrationStateModel.savedInfo.azureAccount?.displayInfo.userId) {
if (account.name.toLowerCase() === this.migrationStateModel.savedInfo.azureAccount?.displayInfo.userId.toLowerCase()) {
selectDropDownIndex(this._azureAccountsDropdown, index);
}
});

View File

@@ -283,7 +283,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
return true;
}).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._disposables.push(this._networkSharePath.onTextChanged(async (value) => {
@@ -331,7 +331,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
return true;
}).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._disposables.push(this._windowsUserAccountText.onTextChanged((value) => {
@@ -458,7 +458,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return flexContainer;
}
private createTargetDatabaseContainer(): azdata.FlexContainer {
const headerCssStyles: azdata.CssStyles = {
...styles.LABEL_CSS,
@@ -755,253 +754,265 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return container;
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this.migrationStateModel.refreshDatabaseBackupPage) {
if (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;
this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = !isOfflineMigration;
this._blobContainerTargetDatabaseNamesTable.columns.forEach(column => {
column.width = isOfflineMigration ? WIZARD_TABLE_COLUMN_WIDTH_SMALL : WIZARD_TABLE_COLUMN_WIDTH;
});
try {
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;
this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = !isOfflineMigration;
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.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
this._networkShareButton.checked = true;
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode)) {
if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
this._networkShareButton.checked = true;
} else {
this._networkShareButton.checked = false;
this._networkTableContainer.display = 'none';
await this._networkShareContainer.updateCssStyles({ 'display': 'none' });
}
} else {
this._networkShareButton.checked = false;
this._networkTableContainer.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.savedInfo.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
this._blobContainerButton.checked = true;
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode)) {
if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
this._blobContainerButton.checked = true;
} else {
this._blobContainerButton.checked = false;
this._blobTableContainer.display = 'none';
await this._blobContainer.updateCssStyles({ 'display': 'none' });
}
} else {
this._blobContainerButton.checked = false;
this._blobTableContainer.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._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' });
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
const query = 'select SUSER_NAME()';
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
const username = results.rows[0][0].displayValue;
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._sqlSourceUsernameInput.value = username;
this._sqlSourcePassword.value = (await azdata.connection.getCredentials(this.migrationStateModel.sourceConnectionId)).password;
await this._targetDatabaseContainer.updateCssStyles({ 'display': 'none' });
await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' });
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
const query = 'select SUSER_NAME()';
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
const username = results.rows[0][0].displayValue;
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._sqlSourceUsernameInput.value = username;
this._sqlSourcePassword.value = (await azdata.connection.getCredentials(this.migrationStateModel.sourceConnectionId)).password;
this._networkShareTargetDatabaseNames = [];
this._blobContainerTargetDatabaseNames = [];
this._blobContainerResourceGroupDropdowns = [];
this._blobContainerStorageAccountDropdowns = [];
this._blobContainerDropdowns = [];
this._blobContainerLastBackupFileDropdowns = [];
this._networkShareTargetDatabaseNames = [];
this._blobContainerTargetDatabaseNames = [];
this._blobContainerResourceGroupDropdowns = [];
this._blobContainerStorageAccountDropdowns = [];
this._blobContainerDropdowns = [];
this._blobContainerLastBackupFileDropdowns = [];
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
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];
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
}
this._networkShareTargetDatabaseNames.push(targetDatabaseInput);
const blobTargetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
required: true,
value: db,
}).withValidation(c => {
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.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 });
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.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
targetDatabaseInput.value = this.migrationStateModel.savedInfo.targetDatabaseNames[index];
} else {
await this.disableBlobTableDropdowns(index, constants.BLOB_CONTAINER);
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
}
}));
this._blobContainerDropdowns.push(blobContainerDropdown);
this._networkShareTargetDatabaseNames.push(targetDatabaseInput);
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);
const blobTargetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
required: true,
value: db,
}).withValidation(c => {
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;
}
});
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;
this.wizard.message = {
text: errorText,
description: error?.stack,
level: azdata.window.MessageLevel.Error
};
}
}
this.wizard.registerNavigationValidator((pageChangeInfo) => {
@@ -1131,7 +1142,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none';
//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;
}
@@ -1150,7 +1161,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
await this.validateFields();
}
private async validateFields(): Promise<void> {
await this._sqlSourceUsernameInput.validate();
await this._sqlSourcePassword.validate();
@@ -1175,7 +1185,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
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._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance;
}
@@ -1196,7 +1206,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._networkShareStorageAccountResourceGroupDropdown.loading = true;
try {
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) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.networkShare?.resourceGroup?.id?.toLowerCase()) {
selectDropDownIndex(this._networkShareStorageAccountResourceGroupDropdown, index);
@@ -1231,7 +1241,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const resourceGroupValues = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
this._blobContainerResourceGroupDropdowns.forEach((dropDown, index) => {
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) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.resourceGroup?.id?.toLowerCase()) {
selectDropDownIndex(dropDown, resourceIndex);
@@ -1251,8 +1261,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadBlobStorageDropdown(index: number): Promise<void> {
this._blobContainerStorageAccountDropdowns[index].loading = true;
try {
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) {
this._blobContainerStorageAccountDropdowns[index].values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.resourceGroup);
if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerStorageAccountDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.storageAccount)) {
this._blobContainerStorageAccountDropdowns[index].values!.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.storageAccount?.id?.toLowerCase()) {
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[index], resourceIndex);
@@ -1271,9 +1281,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadBlobContainerDropdown(index: number): Promise<void> {
this._blobContainerDropdowns[index].loading = true;
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;
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) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.blobContainer?.id?.toLowerCase()) {
selectDropDownIndex(this._blobContainerDropdowns[index], resourceIndex);
@@ -1292,9 +1302,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadBlobLastBackupFileDropdown(index: number): Promise<void> {
this._blobContainerLastBackupFileDropdowns[index].loading = true;
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;
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) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.lastBackupFile!.toLowerCase()) {
selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], resourceIndex);
@@ -1334,4 +1344,12 @@ export class DatabaseBackupPage extends MigrationWizardPage {
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[rowIndex], 0);
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 vscode from 'vscode';
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 { IconPath, IconPathHelper } from '../constants/iconPathHelper';
import { debounce } from '../api/utils';
@@ -275,17 +275,26 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
}
).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);
} 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.updateValuesOnSelection();
}
this._disposables.push(this._databaseSelectorTable.onDataChanged(async () => {
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;
await this.updateValuesOnSelection();
}));
const flex = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
@@ -314,6 +323,14 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
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
private createIconTextCell(icon: IconPath, text: string): string {
return text;

View File

@@ -86,7 +86,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}
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._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance;
}
@@ -391,7 +391,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this._resourceGroupDropdown.loading = true;
try {
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) => {
const resourceId = this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase();
if (resourceId && (<azdata.CategoryValue>resource).name.toLowerCase() === getFullResourceGroupFromId(resourceId)) {
@@ -409,8 +409,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
try {
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());
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._dmsDropdown.values) {
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._dmsDropdown.values)) {
this._dmsDropdown.values.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase()) {
selectDropDownIndex(this._dmsDropdown, resourceIndex);

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
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 styles from '../constants/styles';
@@ -113,7 +113,7 @@ export class MigrationModePage extends MigrationWizardPage {
}
}).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) {
onlineButton.checked = true;
offlineButton.checked = false;

View File

@@ -197,6 +197,14 @@ export class SKURecommendationPage extends MigrationWizardPage {
}));
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 {
@@ -300,7 +308,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
}).component();
let serverName = '';
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.serverName) {
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.serverName)) {
serverName = this.migrationStateModel.serverName;
} else {
serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
@@ -505,7 +513,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
this.migrationStateModel._migrationDbs = miDbs;
} else {
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);
} else {
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);
}
const assessmentError = this.migrationStateModel._assessmentResults.assessmentError;
const assessmentError = this.migrationStateModel._assessmentResults?.assessmentError;
if (assessmentError) {
errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`);
}
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}`)!);
}
@@ -566,11 +574,11 @@ export class SKURecommendationPage extends MigrationWizardPage {
} else {
this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration;
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) {
this._rbg.selectedCardId = this.migrationStateModel.savedInfo.migrationTargetType;
await this.refreshCardText();
@@ -602,9 +610,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
await this.assessmentGroupContainer.updateCssStyles({ 'display': display });
this.assessmentGroupContainer.display = display;
display = this._rbg.selectedCardId
display = (this._rbg.selectedCardId
&& (!failedAssessment || this._skipAssessmentCheckbox.checked)
&& this.migrationStateModel._migrationDbs.length > 0
&& this.migrationStateModel._migrationDbs.length > 0)
? 'inline'
: 'none';
await this._targetContainer.updateCssStyles({ 'display': display });
@@ -614,7 +622,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
}
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;
}
if (!this.migrationStateModel._targetSubscription) {
@@ -628,9 +636,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._managedInstanceSubscriptionDropdown.loading = false;
this._resourceDropdown.loading = false;
}
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= 2 && this._managedInstanceSubscriptionDropdown.values) {
this._managedInstanceSubscriptionDropdown.values.forEach((subscription, index) => {
if ((<azdata.CategoryValue>subscription).name === this.migrationStateModel.savedInfo?.subscription?.id) {
if (this.hasSavedInfo() && this._managedInstanceSubscriptionDropdown.values) {
this._managedInstanceSubscriptionDropdown.values!.forEach((subscription, index) => {
if ((<azdata.CategoryValue>subscription).name.toLowerCase() === this.migrationStateModel.savedInfo?.subscription?.id.toLowerCase()) {
selectDropDownIndex(this._managedInstanceSubscriptionDropdown, index);
}
});
@@ -645,9 +653,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._azureLocationDropdown.loading = true;
try {
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) => {
if (resourceGroup.name === this.migrationStateModel.savedInfo?.resourceGroup?.id) {
if (resourceGroup.name.toLowerCase() === this.migrationStateModel.savedInfo?.resourceGroup?.id.toLowerCase()) {
selectDropDownIndex(this._azureResourceGroupDropdown, index);
}
});
@@ -655,7 +663,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
selectDropDownIndex(this._azureResourceGroupDropdown, 0);
}
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) => {
if (location.displayName === this.migrationStateModel.savedInfo?.location?.displayName) {
selectDropDownIndex(this._azureLocationDropdown, index);
@@ -690,9 +698,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
this.migrationStateModel._location,
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) => {
if (resource.displayName === this.migrationStateModel.savedInfo?.targetServerInstance?.name) {
if (resource.displayName.toLowerCase() === this.migrationStateModel.savedInfo?.targetServerInstance?.name.toLowerCase()) {
selectDropDownIndex(this._resourceDropdown, index);
}
});
@@ -708,9 +716,6 @@ export class SKURecommendationPage extends MigrationWizardPage {
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator((pageChangeInfo) => {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) {
this.migrationStateModel._migrationDbs = this.migrationStateModel.savedInfo.databaseList;
}
const errors: string[] = [];
this.wizard.message = {
text: '',
@@ -785,8 +790,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._targetContainer.display = (this.migrationStateModel._migrationDbs.length === 0) ? 'none' : 'inline';
if (this.migrationStateModel._assessmentResults) {
const dbCount = this.migrationStateModel._assessmentResults.databaseAssessments?.length;
const dbWithoutIssuesCount = this.migrationStateModel._assessmentResults.databaseAssessments?.filter(db => db.issues?.length === 0).length;
const dbCount = this.migrationStateModel._assessmentResults?.databaseAssessments?.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[1].descriptions[1].textValue = constants.CAN_BE_MIGRATED(dbCount, dbCount);
@@ -837,6 +842,10 @@ export class SKURecommendationPage extends MigrationWizardPage {
}).component();
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(', ')));
}
}

View File

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