diff --git a/extensions/sql-migration/src/api/utils.ts b/extensions/sql-migration/src/api/utils.ts index ac2db7aa23..0f907f0539 100644 --- a/extensions/sql-migration/src/api/utils.ts +++ b/extensions/sql-migration/src/api/utils.ts @@ -146,6 +146,15 @@ export function convertIsoTimeToLocalTime(isoTime: string): Date { export type SupportedAutoRefreshIntervals = -1 | 15000 | 30000 | 60000 | 180000 | 300000; +export function selectDefaultDropdownValue(dropDown: DropDownComponent, value?: string, useDisplayName: boolean = true): void { + const selectedIndex = value ? findDropDownItemIndex(dropDown, value, useDisplayName) : -1; + if (selectedIndex > -1) { + selectDropDownIndex(dropDown, selectedIndex); + } else { + selectDropDownIndex(dropDown, 0); + } +} + export function selectDropDownIndex(dropDown: DropDownComponent, index: number): void { if (index >= 0 && dropDown.values && index <= dropDown.values.length - 1) { const value = dropDown.values[index]; @@ -153,10 +162,15 @@ export function selectDropDownIndex(dropDown: DropDownComponent, index: number): } } -export function findDropDownItemIndex(dropDown: DropDownComponent, value: string): number { +export function findDropDownItemIndex(dropDown: DropDownComponent, value: string, useDisplayName: boolean = true): number { if (dropDown.values) { - return dropDown.values.findIndex((v: any) => - (v as CategoryValue)?.displayName?.toLowerCase() === value?.toLowerCase()); + if (useDisplayName) { + return dropDown.values.findIndex((v: any) => + (v as CategoryValue)?.displayName?.toLowerCase() === value?.toLowerCase()); + } else { + return dropDown.values.findIndex((v: any) => + (v as CategoryValue)?.name?.toLowerCase() === value?.toLowerCase()); + } } return -1; diff --git a/extensions/sql-migration/src/constants/helper.ts b/extensions/sql-migration/src/constants/helper.ts index 096cb5572b..9497e2fc65 100644 --- a/extensions/sql-migration/src/constants/helper.ts +++ b/extensions/sql-migration/src/constants/helper.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as azdata from 'azdata'; import { MigrationContext, MigrationStatus } from '../models/migrationLocalStorage'; import { MigrationMode, MigrationTargetType } from '../models/stateMachine'; import * as loc from './strings'; @@ -48,3 +49,20 @@ export function canRetryMigration(status: string | undefined): boolean { status === MigrationStatus.Succeeded || status === MigrationStatus.Canceled; } + + +const TABLE_CHECKBOX_INDEX = 0; +const TABLE_DB_NAME_INDEX = 1; +export function selectDatabasesFromList(selectedDbs: string[], databaseTableValues: azdata.DeclarativeTableCellValue[][]): azdata.DeclarativeTableCellValue[][] { + const sourceDatabaseNames = selectedDbs?.map(dbName => dbName.toLocaleLowerCase()) || []; + if (sourceDatabaseNames?.length > 0) { + for (let i in databaseTableValues) { + const row = databaseTableValues[i]; + const dbName = (row[TABLE_DB_NAME_INDEX].value as string).toLocaleLowerCase(); + if (sourceDatabaseNames.indexOf(dbName) > -1) { + row[TABLE_CHECKBOX_INDEX].value = true; + } + } + } + return databaseTableValues; +} diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index 63b346202d..282b2fd261 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -17,10 +17,13 @@ export function WIZARD_TITLE(instanceName: string): string { } // //#endregion -// Resume Migration Dialog +// Save and close +export const SAVE_AND_CLOSE = localize('sql.migration.save.close', "Save and close"); +export const SAVE_AND_CLOSE_POPUP = localize('sql.migration.save.close.popup', "Configuration saved. Performance data collection will remain running in the background. You can stop the collection when you want to."); export const RESUME_TITLE = localize('sql.migration.resume.title', "Run migration workflow again"); -export const START_MIGRATION = localize('sql.migration.resume.start', "Start a new session"); -export const CONTINUE_MIGRATION = localize('sql.migration.resume.continue', "Resume previously saved session"); +export const START_NEW_SESSION = localize('sql.migration.start.session', "Start a new session"); +export const RESUME_SESSION = localize('sql.migration.resume.session', "Resume previously saved session"); +export const OPEN_SAVED_INFO_ERROR = localize("sql.migration.invalid.savedInfo", 'Cannot retrieve saved session. Try again by selecting new session.'); // Databases for assessment export const DATABASE_FOR_ASSESSMENT_PAGE_TITLE = localize('sql.migration.database.assessment.title', "Databases for assessment"); @@ -267,6 +270,8 @@ export const ACCOUNTS_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.acco export const ACCOUNTS_SELECTION_PAGE_DESCRIPTION = localize('sql.migration.wizard.account.description', "Select an Azure account linked to Azure Data Studio, or link one now."); export const ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR = localize('sql.migration.wizard.account.noAccount.error', "Add a linked account and then try again."); export const ACCOUNT_LINK_BUTTON_LABEL = localize('sql.migration.wizard.account.add.button.label', "Link account"); +export const INVALID_ACCOUNT_ERROR = localize('sql.migration.invalid.account.error', "To continue, select a valid Azure account."); + export function accountLinkedMessage(count: number): string { return count === 1 ? localize('sql.migration.wizard.account.count.single.message', '{0} account linked', count) : localize('sql.migration.wizard.account.count.multiple.message', '{0} accounts linked', count); } @@ -665,8 +670,6 @@ export const SQL_MIGRATION_SERVICE_DETAILS_AUTH_KEYS_TITLE = localize('sql.migra export const SQL_MIGRATION_SERVICE_DETAILS_STATUS_UNAVAILABLE = localize('sql.migration.service.details.status.unavailable', "-- unavailable --"); //Source Credentials page. -export const SAVE_AND_CLOSE = localize('sql.migration.save.close', "Save and close"); -export const SAVE_AND_CLOSE_POPUP = localize('sql.migration.save.close.popup', "Configuration saved. Performance data collection will remain running in the background. You can stop the collection when you want to."); export const SOURCE_CONFIGURATION = localize('sql.migration.source.configuration', "Source configuration"); export const SOURCE_CREDENTIALS = localize('sql.migration.source.credentials', "Source credentials"); export const ENTER_YOUR_SQL_CREDS = localize('sql.migration.enter.your.sql.cred', "Enter the credentials for the source SQL Server instance. These credentials will be used while migrating databases to Azure SQL."); diff --git a/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts b/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts index 16c1a6e3a2..3e52be92b1 100644 --- a/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts +++ b/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts @@ -5,7 +5,7 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { MigrationStateModel, MigrationTargetType, Page } from '../../models/stateMachine'; +import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine'; import { SqlDatabaseTree } from './sqlDatabasesTree'; import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql'; import { SKURecommendationPage } from '../../wizard/skuRecommendationPage'; @@ -32,9 +32,6 @@ 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 >= Page.DatabaseBackup) { - this._model._databaseAssessment = this._model.savedInfo.databaseAssessment; - } this._tree = new SqlDatabaseTree(this._model, this._targetType); } @@ -87,16 +84,31 @@ export class AssessmentResultsDialog { } protected async execute() { - if (this._targetType === MigrationTargetType.SQLVM) { - this._model._vmDbs = this._tree.selectedDbs(); - } else { - this._model._miDbs = this._tree.selectedDbs(); + const selectedDbs = this._tree.selectedDbs(); + switch (this._targetType) { + case MigrationTargetType.SQLMI: { + this.didUpdateDatabasesForMigration(this._model._miDbs, selectedDbs); + this._model._miDbs = selectedDbs; + break; + } + + case MigrationTargetType.SQLVM: { + this.didUpdateDatabasesForMigration(this._model._vmDbs, selectedDbs); + this._model._vmDbs = selectedDbs; + break; + } } await this._skuRecommendationPage.refreshCardText(); this.model.refreshDatabaseBackupPage = true; this._isOpen = false; } + private didUpdateDatabasesForMigration(priorDbs: string[], selectedDbs: string[]) { + this._model._didUpdateDatabasesForMigration = selectedDbs.length === 0 + || selectedDbs.length !== priorDbs.length + || priorDbs.some(db => selectedDbs.indexOf(db) < 0); + } + protected async cancel() { this._isOpen = false; } diff --git a/extensions/sql-migration/src/dialog/assessmentResults/savedAssessmentDialog.ts b/extensions/sql-migration/src/dialog/assessmentResults/savedAssessmentDialog.ts index e5abce8f3e..65a3697837 100644 --- a/extensions/sql-migration/src/dialog/assessmentResults/savedAssessmentDialog.ts +++ b/extensions/sql-migration/src/dialog/assessmentResults/savedAssessmentDialog.ts @@ -50,6 +50,16 @@ export class SavedAssessmentDialog { reject(ex); } }); + + dialog.registerCloseValidator(async () => { + if (this.stateModel.resumeAssessment) { + if (!this.stateModel.loadSavedInfo()) { + void vscode.window.showInformationMessage(constants.OPEN_SAVED_INFO_ERROR); + return false; + } + } + return true; + }); }); } @@ -67,14 +77,8 @@ export class SavedAssessmentDialog { } protected async execute() { - if (this.stateModel.resumeAssessment) { - const wizardController = new WizardController(this.context, this.stateModel); - await wizardController.openWizard(this.stateModel.sourceConnectionId); - } else { - // normal flow - const wizardController = new WizardController(this.context, this.stateModel); - await wizardController.openWizard(this.stateModel.sourceConnectionId); - } + const wizardController = new WizardController(this.context, this.stateModel); + await wizardController.openWizard(this.stateModel.sourceConnectionId); this._isOpen = false; } @@ -90,7 +94,7 @@ export class SavedAssessmentDialog { const buttonGroup = 'resumeMigration'; const radioStart = view.modelBuilder.radioButton().withProps({ - label: constants.START_MIGRATION, + label: constants.START_NEW_SESSION, name: buttonGroup, CSSStyles: { ...styles.BODY_CSS, @@ -105,7 +109,7 @@ export class SavedAssessmentDialog { } }); const radioContinue = view.modelBuilder.radioButton().withProps({ - label: constants.CONTINUE_MIGRATION, + label: constants.RESUME_SESSION, name: buttonGroup, CSSStyles: { ...styles.BODY_CSS, @@ -122,11 +126,9 @@ export class SavedAssessmentDialog { const flex = view.modelBuilder.flexContainer() .withLayout({ flexFlow: 'column', - height: '100%', - width: '100%', }).withProps({ CSSStyles: { - 'margin': '20px 15px', + 'padding': '20px 15px', } }).component(); flex.addItem(radioStart, { flex: '0 0 auto' }); @@ -134,5 +136,4 @@ export class SavedAssessmentDialog { return flex; } - } diff --git a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts index cedbf6ff98..51d73e1106 100644 --- a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts +++ b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts @@ -5,12 +5,13 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql'; -import { MigrationStateModel, MigrationTargetType, Page } from '../../models/stateMachine'; +import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine'; import * as constants from '../../constants/strings'; import { debounce } from '../../api/utils'; import { IconPath, IconPathHelper } from '../../constants/iconPathHelper'; import * as styles from '../../constants/styles'; import { EOL } from 'os'; +import { selectDatabasesFromList } from '../../constants/helper'; const styleLeft: azdata.CssStyles = { 'border': 'none', @@ -142,7 +143,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._databasesForAssessment?.length) }).component(); return this._databaseCount; } @@ -881,7 +882,7 @@ export class SqlDatabaseTree { public async initialize(): Promise { let instanceTableValues: azdata.DeclarativeTableCellValue[][] = []; this._databaseTableValues = []; - this._dbNames = this._model._databaseAssessment; + this._dbNames = this._model._databasesForAssessment; const selectedDbs = (this._targetType === MigrationTargetType.SQLVM) ? this._model._vmDbs : this._model._miDbs; this._serverName = (await this._model.getSourceConnectionProfile()).serverName; @@ -959,25 +960,16 @@ export class SqlDatabaseTree { }); } await this._instanceTable.setDataValues(instanceTableValues); - 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(); - } + this._databaseTableValues = selectDatabasesFromList(this._model._databasesForMigration, 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) + 'value': constants.DATABASES(this.selectedDbs()?.length, this._model._databasesForAssessment?.length) }); - this._model._databaseSelection = this._databaseTable.dataValues; } // undo when bug #16445 is fixed diff --git a/extensions/sql-migration/src/dialog/retryMigration/retryMigrationDialog.ts b/extensions/sql-migration/src/dialog/retryMigration/retryMigrationDialog.ts index 0a6d7b85d1..28ee1f98a5 100644 --- a/extensions/sql-migration/src/dialog/retryMigration/retryMigrationDialog.ts +++ b/extensions/sql-migration/src/dialog/retryMigration/retryMigrationDialog.ts @@ -12,6 +12,7 @@ import { MigrationMode, MigrationStateModel, NetworkContainerType, SavedInfo } f import { MigrationContext } from '../../models/migrationLocalStorage'; import { WizardController } from '../../wizard/wizardController'; import { getMigrationModeEnum, getMigrationTargetTypeEnum } from '../../constants/helper'; +import * as constants from '../../constants/strings'; export class RetryMigrationDialog { private _context: vscode.ExtensionContext; @@ -30,21 +31,18 @@ export class RetryMigrationDialog { savedInfo = { closedPage: 0, - // AzureAccount - azureAccount: migration.azureAccount, - azureTenant: migration.azureAccount.properties.tenants[0], - // DatabaseSelector - selectedDatabases: [], + databaseAssessment: [sourceDatabaseName], // SKURecommendation - databaseAssessment: [], databaseList: [sourceDatabaseName], - migrationDatabases: [], serverAssessment: null, skuRecommendation: null, - migrationTargetType: getMigrationTargetTypeEnum(migration)!, + + // TargetSelection + azureAccount: migration.azureAccount, + azureTenant: migration.azureAccount.properties.tenants[0], subscription: migration.subscription, location: location, resourceGroup: { @@ -58,14 +56,13 @@ export class RetryMigrationDialog { migrationMode: getMigrationModeEnum(migration), // DatabaseBackup - targetSubscription: migration.subscription, targetDatabaseNames: [migration.migrationContext.name], networkContainerType: null, networkShares: [], blobs: [], // Integration Runtime - migrationServiceId: migration.migrationContext.properties.migrationService, + sqlMigrationService: migration.controller, }; const getStorageAccountResourceGroup = (storageAccountResourceId: string) => { @@ -151,7 +148,11 @@ export class RetryMigrationDialog { 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); + if (stateModel.loadSavedInfo()) { + const wizardController = new WizardController(this._context, stateModel); + await wizardController.openWizard(stateModel.sourceConnectionId); + } else { + void vscode.window.showInformationMessage(constants.MIGRATION_CANNOT_RETRY); + } } } diff --git a/extensions/sql-migration/src/dialog/targetDatabaseSummary/targetDatabaseSummaryDialog.ts b/extensions/sql-migration/src/dialog/targetDatabaseSummary/targetDatabaseSummaryDialog.ts index ea0f387187..dca1fce252 100644 --- a/extensions/sql-migration/src/dialog/targetDatabaseSummary/targetDatabaseSummaryDialog.ts +++ b/extensions/sql-migration/src/dialog/targetDatabaseSummary/targetDatabaseSummaryDialog.ts @@ -35,7 +35,7 @@ export class TargetDatabaseSummaryDialog { this._view = view; const databaseCount = this._view.modelBuilder.text().withProps({ - value: constants.COUNT_DATABASES(this._model._migrationDbs.length), + value: constants.COUNT_DATABASES(this._model._databasesForMigration.length), CSSStyles: { ...styles.BODY_CSS, 'margin-bottom': '20px' @@ -132,7 +132,7 @@ export class TargetDatabaseSummaryDialog { const tableRows: azdata.DeclarativeTableCellValue[][] = []; - this._model._migrationDbs.forEach((db, index) => { + this._model._databasesForMigration.forEach((db, index) => { const tableRow: azdata.DeclarativeTableCellValue[] = []; tableRow.push({ value: db diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts index 903039cfd2..bec5abb083 100644 --- a/extensions/sql-migration/src/models/stateMachine.ts +++ b/extensions/sql-migration/src/models/stateMachine.ts @@ -118,25 +118,22 @@ export interface StateChangeEvent { export interface SavedInfo { closedPage: number; - serverAssessment: ServerAssessment | null; + databaseAssessment: string[]; + databaseList: string[]; + migrationTargetType: MigrationTargetType | null; azureAccount: azdata.Account | null; azureTenant: azurecore.Tenant | null; - selectedDatabases: azdata.DeclarativeTableCellValue[][]; - migrationTargetType: MigrationTargetType | null; - migrationDatabases: azdata.DeclarativeTableCellValue[][]; - databaseList: string[]; subscription: azureResource.AzureResourceSubscription | null; location: azureResource.AzureLocation | null; resourceGroup: azureResource.AzureResourceResourceGroup | null; targetServerInstance: azureResource.AzureSqlManagedInstance | SqlVMServer | null; migrationMode: MigrationMode | null; - databaseAssessment: string[] | null; networkContainerType: NetworkContainerType | null; networkShares: NetworkShare[]; - targetSubscription: azureResource.AzureResourceSubscription | null; blobs: Blob[]; targetDatabaseNames: string[]; - migrationServiceId: string | null; + sqlMigrationService: SqlMigrationService | undefined; + serverAssessment: ServerAssessment | null; skuRecommendation: SkuRecommendationSavedInfo | null; } @@ -160,10 +157,8 @@ export class MigrationStateModel implements Model, vscode.Disposable { public _authenticationType!: MigrationSourceAuthenticationType; public _sqlServerUsername!: string; public _sqlServerPassword!: string; - public _databaseAssessment!: string[]; public _subscriptions!: azureResource.AzureResourceSubscription[]; - public _targetSubscription!: azureResource.AzureResourceSubscription; public _locations!: azureResource.AzureLocation[]; public _location!: azureResource.AzureLocation; @@ -173,11 +168,11 @@ export class MigrationStateModel implements Model, vscode.Disposable { public _targetSqlVirtualMachines!: SqlVMServer[]; public _targetServerInstance!: SqlManagedInstance | SqlVMServer; public _databaseBackup!: DatabaseBackupModel; - public _migrationDbs: string[] = []; public _storageAccounts!: StorageAccount[]; public _fileShares!: azureResource.FileShare[]; public _blobContainers!: azureResource.BlobContainer[]; public _lastFileNames!: azureResource.Blob[]; + public _sourceDatabaseNames!: string[]; public _targetDatabaseNames!: string[]; public _sqlMigrationServiceResourceGroup!: string; @@ -189,11 +184,19 @@ export class MigrationStateModel implements Model, vscode.Disposable { private _currentState: State; private _gatheringInformationError: string | undefined; + public _databasesForAssessment!: string[]; public _assessmentResults!: ServerAssessment; + public _assessedDatabaseList!: string[]; public _runAssessments: boolean = true; private _assessmentApiResponse!: mssql.AssessmentResult; public mementoString: string; + public _databasesForMigration: string[] = []; + public _didUpdateDatabasesForMigration: boolean = false; + public _vmDbs: string[] = []; + public _miDbs: string[] = []; + public _targetType!: MigrationTargetType; + public _skuRecommendationResults!: SkuRecommendation; public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions; private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult; @@ -225,12 +228,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { public _skuTargetPercentile!: number; public _skuEnablePreview!: boolean; - public _vmDbs: string[] = []; - public _miDbs: string[] = []; - public _targetType!: MigrationTargetType; public refreshDatabaseBackupPage!: boolean; - - public _databaseSelection!: azdata.DeclarativeTableCellValue[][]; public retryMigration!: boolean; public resumeAssessment!: boolean; public savedInfo!: SavedInfo; @@ -244,7 +242,6 @@ export class MigrationStateModel implements Model, vscode.Disposable { 'model' ]; public serverName!: string; - public databaseSelectorTableValues!: azdata.DeclarativeTableCellValue[][]; constructor( public extensionContext: vscode.ExtensionContext, @@ -255,6 +252,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { this._databaseBackup = {} as DatabaseBackupModel; this._databaseBackup.networkShares = []; this._databaseBackup.blobs = []; + this._targetDatabaseNames = []; this.mementoString = 'sqlMigration.assessmentResults'; this._skuScalingFactor = 100; @@ -282,7 +280,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { } public hasRecommendedDatabaseListChanged(): boolean { const oldDbList = this._skuRecommendationRecommendedDatabaseList; - const newDbList = this._databaseAssessment; + const newDbList = this._databasesForAssessment; if (!oldDbList || !newDbList) { return false; @@ -295,8 +293,10 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getDatabaseAssessments(targetType: MigrationTargetType): Promise { const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId); try { - const response = (await this.migrationService.getAssessments(ownerUri, this._databaseAssessment))!; + const response = (await this.migrationService.getAssessments(ownerUri, this._databasesForAssessment))!; this._assessmentApiResponse = response; + this._assessedDatabaseList = this._databasesForAssessment.slice(); + if (response?.assessmentResult) { response.assessmentResult.items = response.assessmentResult.items?.filter( issue => issue.appliesToMigrationTargetPlatform === targetType); @@ -318,7 +318,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { } else { this._assessmentResults = { issues: [], - databaseAssessments: this._databaseAssessment?.map(database => { + databaseAssessments: this._databasesForAssessment?.map(database => { return { name: database, issues: [], @@ -332,7 +332,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { } catch (error) { this._assessmentResults = { issues: [], - databaseAssessments: this._databaseAssessment?.map(database => { + databaseAssessments: this._databasesForAssessment?.map(database => { return { name: database, issues: [], @@ -364,11 +364,11 @@ export class MigrationStateModel implements Model, vscode.Disposable { this._defaultDataPointStartTime, this._defaultDataPointEndTime, this._skuEnablePreview, - this._databaseAssessment))!; + this._databasesForAssessment))!; this._skuRecommendationApiResponse = response; // clone list of databases currently being assessed and store them, so that if the user ever changes the list we can refresh new recommendations - this._skuRecommendationRecommendedDatabaseList = this._databaseAssessment.slice(); + this._skuRecommendationRecommendedDatabaseList = this._databasesForAssessment.slice(); if (response?.sqlDbRecommendationResults || response?.sqlMiRecommendationResults || response?.sqlVmRecommendationResults) { this._skuRecommendationResults = { @@ -862,9 +862,12 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getSubscriptionsDropdownValues(): Promise { let subscriptionsValues: azdata.CategoryValue[] = []; try { - if (!this._subscriptions) { + if (this._azureAccount) { this._subscriptions = await getSubscriptions(this._azureAccount); + } else { + this._subscriptions = []; } + this._subscriptions.forEach((subscription) => { subscriptionsValues.push({ name: subscription.id, @@ -900,7 +903,12 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getAzureLocationDropdownValues(subscription: azureResource.AzureResourceSubscription): Promise { let locationValues: azdata.CategoryValue[] = []; try { - this._locations = await getLocations(this._azureAccount, subscription); + if (this._azureAccount && subscription) { + this._locations = await getLocations(this._azureAccount, subscription); + } else { + this._locations = []; + } + this._locations.forEach((loc) => { locationValues.push({ name: loc.name, @@ -911,7 +919,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { if (locationValues.length === 0) { locationValues = [ { - displayName: constants.INVALID_LOCATION_ERROR, + displayName: constants.NO_LOCATION_FOUND, name: '' } ]; @@ -920,7 +928,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { console.log(e); locationValues = [ { - displayName: constants.INVALID_LOCATION_ERROR, + displayName: constants.NO_LOCATION_FOUND, name: '' } ]; @@ -940,7 +948,12 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getAzureResourceGroupDropdownValues(subscription: azureResource.AzureResourceSubscription): Promise { let resourceGroupValues: azdata.CategoryValue[] = []; try { - this._resourceGroups = await getResourceGroups(this._azureAccount, subscription); + if (this._azureAccount && subscription) { + this._resourceGroups = await getResourceGroups(this._azureAccount, subscription); + } else { + this._resourceGroups = []; + } + this._resourceGroups.forEach((rg) => { resourceGroupValues.push({ name: rg.id, @@ -974,16 +987,18 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getManagedInstanceValues(subscription: azureResource.AzureResourceSubscription, location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise { let managedInstanceValues: azdata.CategoryValue[] = []; - 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()) { - return true; - } - return false; - }); + if (this._azureAccount && subscription && location && resourceGroup) { + this._targetManagedInstances = (await getAvailableManagedInstanceProducts(this._azureAccount, subscription)).filter((mi) => { + if (mi.location.toLowerCase() === location?.name.toLowerCase() && mi.resourceGroup?.toLowerCase() === resourceGroup?.name.toLowerCase()) { + return true; + } + return false; + }); + } else { + this._targetManagedInstances = []; + } + this._targetManagedInstances.forEach((managedInstance) => { managedInstanceValues.push({ name: managedInstance.id, @@ -1024,7 +1039,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getSqlVirtualMachineValues(subscription: azureResource.AzureResourceSubscription, location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise { let virtualMachineValues: azdata.CategoryValue[] = []; try { - if (this._azureAccount && subscription && resourceGroup) { + if (this._azureAccount && subscription && location && resourceGroup) { this._targetSqlVirtualMachines = (await getAvailableSqlVMs(this._azureAccount, subscription, resourceGroup)).filter((virtualMachine) => { if (virtualMachine?.location?.toLowerCase() === location?.name?.toLowerCase()) { if (virtualMachine.properties.sqlImageOffer) { @@ -1074,10 +1089,15 @@ export class MigrationStateModel implements Model, vscode.Disposable { return storageAccountValues; } try { - const storageAccount = (await getAvailableStorageAccounts(this._azureAccount, subscription)); - this._storageAccounts = storageAccount.filter(sa => { - return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase(); - }); + if (this._azureAccount && subscription && resourceGroup) { + const storageAccount = (await getAvailableStorageAccounts(this._azureAccount, subscription)); + this._storageAccounts = storageAccount.filter(sa => { + return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase(); + }); + } else { + this._storageAccounts = []; + } + this._storageAccounts.forEach((storageAccount) => { storageAccountValues.push({ name: storageAccount.id, @@ -1112,7 +1132,12 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getFileShareValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise { let fileShareValues: azdata.CategoryValue[] = []; try { - this._fileShares = await getFileShares(this._azureAccount, subscription, storageAccount); + if (this._azureAccount && subscription && storageAccount) { + this._fileShares = await getFileShares(this._azureAccount, subscription, storageAccount); + } else { + this._fileShares = []; + } + this._fileShares.forEach((fileShare) => { fileShareValues.push({ name: fileShare.id, @@ -1146,17 +1171,13 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getBlobContainerValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise { let blobContainerValues: azdata.CategoryValue[] = []; - if (!this._azureAccount || !subscription || !storageAccount) { - blobContainerValues = [ - { - displayName: constants.NO_BLOBCONTAINERS_FOUND, - name: '' - } - ]; - return blobContainerValues; - } try { - this._blobContainers = await getBlobContainers(this._azureAccount, subscription, storageAccount); + if (this._azureAccount && subscription && storageAccount) { + this._blobContainers = await getBlobContainers(this._azureAccount, subscription, storageAccount); + } else { + this._blobContainers = []; + } + this._blobContainers.forEach((blobContainer) => { blobContainerValues.push({ name: blobContainer.id, @@ -1191,21 +1212,26 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getBlobLastBackupFileNameValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount, blobContainer: azureResource.BlobContainer): Promise { let blobLastBackupFileValues: azdata.CategoryValue[] = []; try { - this._lastFileNames = await getBlobs(this._azureAccount, subscription, storageAccount, blobContainer.name); - if (this._lastFileNames.length === 0) { + if (this._azureAccount && subscription && storageAccount && blobContainer) { + this._lastFileNames = await getBlobs(this._azureAccount, subscription, storageAccount, blobContainer.name); + } else { + this._lastFileNames = []; + } + + this._lastFileNames.forEach((blob) => { + blobLastBackupFileValues.push({ + name: blob.name, + displayName: `${blob.name}`, + }); + }); + + if (blobLastBackupFileValues.length === 0) { blobLastBackupFileValues = [ { displayName: constants.NO_BLOBFILES_FOUND, name: '' } ]; - } else { - this._lastFileNames.forEach((blob) => { - blobLastBackupFileValues.push({ - name: blob.name, - displayName: `${blob.name}`, - }); - }); } } catch (e) { console.log(e); @@ -1220,13 +1246,18 @@ export class MigrationStateModel implements Model, vscode.Disposable { } public getBlobLastBackupFileName(index: number): string { - return this._lastFileNames[index].name; + return this._lastFileNames[index]?.name; } - public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance, resourceGroupName: string): Promise { + public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, resourceGroupName: string): Promise { let sqlMigrationServiceValues: azdata.CategoryValue[] = []; try { - this._sqlMigrationServices = (await getSqlMigrationServices(this._azureAccount, subscription, resourceGroupName?.toLowerCase(), this._sessionId)).filter(sms => sms.location.toLowerCase() === this._targetServerInstance.location.toLowerCase()); + if (this._azureAccount && subscription && resourceGroupName && this._targetServerInstance) { + this._sqlMigrationServices = (await getSqlMigrationServices(this._azureAccount, subscription, resourceGroupName?.toLowerCase(), this._sessionId)).filter(sms => sms.location.toLowerCase() === this._targetServerInstance.location.toLowerCase()); + } else { + this._sqlMigrationServices = []; + } + this._sqlMigrationServices.forEach((sqlMigrationService) => { sqlMigrationServiceValues.push({ name: sqlMigrationService.id, @@ -1289,7 +1320,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { } }; - for (let i = 0; i < this._migrationDbs.length; i++) { + for (let i = 0; i < this._databasesForMigration.length; i++) { try { switch (this._databaseBackup.networkContainerType) { case NetworkContainerType.BLOB_CONTAINER: @@ -1327,7 +1358,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { }; break; } - requestBody.properties.sourceDatabaseName = this._migrationDbs[i]; + requestBody.properties.sourceDatabaseName = this._databasesForMigration[i]; const response = await startDatabaseMigration( this._azureAccount, this._targetSubscription, @@ -1337,7 +1368,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { requestBody, this._sessionId ); - response.databaseMigration.properties.sourceDatabaseName = this._migrationDbs[i]; + response.databaseMigration.properties.sourceDatabaseName = this._databasesForMigration[i]; response.databaseMigration.properties.backupConfiguration = requestBody.properties.backupConfiguration!; response.databaseMigration.properties.offlineConfiguration = requestBody.properties.offlineConfiguration!; @@ -1359,7 +1390,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { 'location': this._targetServerInstance.location, 'targetType': this._targetType, 'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name), - 'hashedDatabaseName': hashString(this._migrationDbs[i]), + 'hashedDatabaseName': hashString(this._databasesForMigration[i]), 'migrationMode': isOfflineMigration ? 'offline' : 'online', 'migrationStartTime': new Date().toString(), 'targetDatabaseName': this._targetDatabaseNames[i], @@ -1382,7 +1413,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { response.asyncUrl, this._sessionId ); - void vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1} - {2}', this._migrationDbs[i], this._targetServerInstance.name, this._targetDatabaseNames[i])); + void vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1} - {2}', this._databasesForMigration[i], this._targetServerInstance.name, this._targetDatabaseNames[i])); } } catch (e) { void vscode.window.showErrorMessage( @@ -1404,37 +1435,33 @@ export class MigrationStateModel implements Model, vscode.Disposable { let saveInfo: SavedInfo; saveInfo = { closedPage: currentPage, - serverAssessment: null, + databaseAssessment: [], + databaseList: [], + migrationTargetType: null, azureAccount: null, azureTenant: null, - selectedDatabases: [], - migrationTargetType: null, - migrationDatabases: [], - databaseList: [], subscription: null, location: null, resourceGroup: null, targetServerInstance: null, migrationMode: null, - databaseAssessment: null, networkContainerType: null, networkShares: [], - targetSubscription: null, blobs: [], targetDatabaseNames: [], - migrationServiceId: null, + sqlMigrationService: undefined, + serverAssessment: null, skuRecommendation: null, }; switch (currentPage) { case Page.Summary: case Page.IntegrationRuntime: - saveInfo.migrationServiceId = this._sqlMigrationService?.id!; + saveInfo.sqlMigrationService = this._sqlMigrationService; case Page.DatabaseBackup: saveInfo.networkContainerType = this._databaseBackup.networkContainerType; saveInfo.networkShares = this._databaseBackup.networkShares; - saveInfo.targetSubscription = this._databaseBackup.subscription; saveInfo.blobs = this._databaseBackup.blobs; saveInfo.targetDatabaseNames = this._targetDatabaseNames; @@ -1451,10 +1478,8 @@ export class MigrationStateModel implements Model, vscode.Disposable { case Page.SKURecommendation: saveInfo.migrationTargetType = this._targetType; - saveInfo.databaseAssessment = this._databaseAssessment; + saveInfo.databaseList = this._databasesForMigration; saveInfo.serverAssessment = this._assessmentResults; - saveInfo.migrationDatabases = this._databaseSelection; - saveInfo.databaseList = this._migrationDbs; if (this._skuRecommendationPerformanceDataSource) { let skuRecommendation: SkuRecommendationSavedInfo = { @@ -1470,10 +1495,68 @@ export class MigrationStateModel implements Model, vscode.Disposable { } case Page.DatabaseSelector: - saveInfo.selectedDatabases = this.databaseSelectorTableValues; + saveInfo.databaseAssessment = this._databasesForAssessment; await this.extensionContext.globalState.update(`${this.mementoString}.${serverName}`, saveInfo); } } + + public loadSavedInfo(): Boolean { + try { + this._targetType = this.savedInfo.migrationTargetType || undefined!; + + this._databasesForAssessment = this.savedInfo.databaseAssessment; + this._databasesForMigration = this.savedInfo.databaseList; + this._didUpdateDatabasesForMigration = true; + switch (this._targetType) { + case MigrationTargetType.SQLMI: + this._miDbs = this._databasesForMigration; + break; + case MigrationTargetType.SQLVM: + this._vmDbs = this._databasesForMigration; + break; + } + + this._azureAccount = this.savedInfo.azureAccount || undefined!; + this._azureTenant = this.savedInfo.azureTenant || undefined!; + + this._targetSubscription = this.savedInfo.subscription || undefined!; + this._location = this.savedInfo.location || undefined!; + this._resourceGroup = this.savedInfo.resourceGroup || undefined!; + this._targetServerInstance = this.savedInfo.targetServerInstance || undefined!; + + this._databaseBackup.migrationMode = this.savedInfo.migrationMode || undefined!; + + this.refreshDatabaseBackupPage = true; + this._sourceDatabaseNames = this._databasesForMigration; + this._targetDatabaseNames = this.savedInfo.targetDatabaseNames; + this._databaseBackup.networkContainerType = this.savedInfo.networkContainerType || undefined!; + this._databaseBackup.networkShares = this.savedInfo.networkShares; + this._databaseBackup.blobs = this.savedInfo.blobs; + this._databaseBackup.subscription = this.savedInfo.subscription || undefined!; + + this._sqlMigrationService = this.savedInfo.sqlMigrationService; + + const savedAssessmentResults = this.savedInfo.serverAssessment; + if (savedAssessmentResults) { + this._assessmentResults = savedAssessmentResults; + this._assessedDatabaseList = this.savedInfo.databaseAssessment; + } + + const savedSkuRecommendation = this.savedInfo.skuRecommendation; + if (savedSkuRecommendation) { + this._skuRecommendationPerformanceDataSource = savedSkuRecommendation.skuRecommendationPerformanceDataSource; + this._skuRecommendationPerformanceLocation = savedSkuRecommendation.skuRecommendationPerformanceLocation; + this._perfDataCollectionStartDate = savedSkuRecommendation.perfDataCollectionStartDate; + this._perfDataCollectionStopDate = savedSkuRecommendation.perfDataCollectionStopDate; + this._skuTargetPercentile = savedSkuRecommendation.skuTargetPercentile; + this._skuScalingFactor = savedSkuRecommendation.skuScalingFactor; + this._skuEnablePreview = savedSkuRecommendation.skuEnablePreview; + } + return true; + } catch { + return false; + } + } } export interface ServerAssessment { diff --git a/extensions/sql-migration/src/telemtery.ts b/extensions/sql-migration/src/telemtery.ts index ab4d746acf..e702dbefaf 100644 --- a/extensions/sql-migration/src/telemtery.ts +++ b/extensions/sql-migration/src/telemtery.ts @@ -19,6 +19,7 @@ export enum TelemetryViews { MigrationCutoverDialog = 'MigrationCutoverDialog', MigrationStatusDialog = 'MigrationStatusDialog', MigrationWizardAccountSelectionPage = 'MigrationWizardAccountSelectionPage', + MigrationWizardTaSkuRecommendationPage = 'MigrationWizardTaSkuRecommendationPage', MigrationWizardTargetSelectionPage = 'MigrationWizardTargetSelectionPage', MigrationWizardIntegrationRuntimePage = 'MigrationWizardIntegrationRuntimePage', MigrationWizardSummaryPage = 'MigrationWizardSummaryPage', diff --git a/extensions/sql-migration/src/wizard/databaseBackupPage.ts b/extensions/sql-migration/src/wizard/databaseBackupPage.ts index f78723ae5b..7d0160e208 100644 --- a/extensions/sql-migration/src/wizard/databaseBackupPage.ts +++ b/extensions/sql-migration/src/wizard/databaseBackupPage.ts @@ -6,15 +6,14 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { EOL } from 'os'; -import { getStorageAccountAccessKeys, SqlManagedInstance, SqlVMServer, Subscription } from '../api/azure'; +import { getStorageAccountAccessKeys } from '../api/azure'; import { MigrationWizardPage } from '../models/migrationWizardPage'; -import { Blob, MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, NetworkShare, Page, StateChangeEvent } from '../models/stateMachine'; +import { Blob, MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, NetworkShare, StateChangeEvent } from '../models/stateMachine'; import * as constants from '../constants/strings'; import { IconPathHelper } from '../constants/iconPathHelper'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; -import { findDropDownItemIndex, selectDropDownIndex } from '../api/utils'; +import { findDropDownItemIndex, selectDropDownIndex, selectDefaultDropdownValue } from '../api/utils'; import { logError, TelemetryViews } from '../telemtery'; -import { azureResource } from 'azureResource'; import * as styles from '../constants/styles'; const WIZARD_TABLE_COLUMN_WIDTH = '200px'; @@ -128,6 +127,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { .withProps({ name: buttonGroup, label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL, + checked: this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE, CSSStyles: { ...styles.BODY_CSS, 'margin': '0' @@ -144,6 +144,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { .withProps({ name: buttonGroup, label: constants.DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL, + checked: this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER, CSSStyles: { ...styles.BODY_CSS, 'margin': '0' @@ -298,9 +299,6 @@ export class DatabaseBackupPage extends MigrationWizardPage { } return true; }).component(); - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) { - this._windowsUserAccountText.value = this.migrationStateModel.savedInfo.networkShares[0].windowsUser; - } this._disposables.push(this._windowsUserAccountText.onTextChanged((value) => { for (let i = 0; i < this.migrationStateModel._databaseBackup.networkShares.length; i++) { this.migrationStateModel._databaseBackup.networkShares[i].windowsUser = value; @@ -545,7 +543,11 @@ export class DatabaseBackupPage extends MigrationWizardPage { this._networkTableContainer = this._view.modelBuilder.flexContainer().withItems([ networkShareTableText, this._networkShareTargetDatabaseNamesTable - ]).component(); + ]).withProps({ + CSSStyles: { + 'display': 'none', + } + }).component(); const allFieldsRequiredLabel = this._view.modelBuilder.text() .withProps({ @@ -559,7 +561,11 @@ export class DatabaseBackupPage extends MigrationWizardPage { blobTableText, allFieldsRequiredLabel, this._blobContainerTargetDatabaseNamesTable - ]).component(); + ]).withProps({ + CSSStyles: { + 'display': 'none', + } + }).component(); const container = this._view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' @@ -567,7 +573,9 @@ export class DatabaseBackupPage extends MigrationWizardPage { this._networkTableContainer, this._blobTableContainer ]).withProps({ - display: 'none' + CSSStyles: { + 'display': 'none', + } }).component(); return container; } @@ -736,18 +744,8 @@ export class DatabaseBackupPage extends MigrationWizardPage { } public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise { - if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) { - this.migrationStateModel._databaseBackup.networkContainerType = this.migrationStateModel.savedInfo.networkContainerType; - this.migrationStateModel._databaseBackup.networkShares = this.migrationStateModel.savedInfo.networkShares; - this.migrationStateModel._databaseBackup.subscription = this.migrationStateModel.savedInfo.targetSubscription; - this.migrationStateModel._databaseBackup.blobs = this.migrationStateModel.savedInfo.blobs; - this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames; - } if (this.migrationStateModel.refreshDatabaseBackupPage) { 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; @@ -755,30 +753,8 @@ export class DatabaseBackupPage extends MigrationWizardPage { column.width = isOfflineMigration ? WIZARD_TABLE_COLUMN_WIDTH_SMALL : WIZARD_TABLE_COLUMN_WIDTH; }); - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) { - 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' }); - } - } + await this.switchNetworkContainerFields(this.migrationStateModel._databaseBackup.networkContainerType); - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) { - 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' }); - } - } - - if (!(this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) { - await this._targetDatabaseContainer.updateCssStyles({ 'display': 'none' }); - await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' }); - } const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile(); const queryProvider = azdata.dataprotocol.getProvider((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider); const query = 'select SUSER_NAME()'; @@ -788,6 +764,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { 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._windowsUserAccountText.value = this.migrationStateModel.savedInfo?.networkShares[0]?.windowsUser; this._networkShareTargetDatabaseNames = []; this._networkShareLocations = []; @@ -800,16 +777,45 @@ export class DatabaseBackupPage extends MigrationWizardPage { if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) { this._existingDatabases = await this.migrationStateModel.getManagedDatabases(); } - this.migrationStateModel._targetDatabaseNames = []; - this.migrationStateModel._databaseBackup.blobs = []; - this.migrationStateModel._databaseBackup.networkShares = []; - this.migrationStateModel._migrationDbs.forEach((db, index) => { - this.migrationStateModel._targetDatabaseNames.push(db); - this.migrationStateModel._databaseBackup.blobs.push({}); - this.migrationStateModel._databaseBackup.networkShares.push({}); + + let originalTargetDatabaseNames = this.migrationStateModel._targetDatabaseNames; + let originalNetworkShares = this.migrationStateModel._databaseBackup.networkShares; + let originalBlobs = this.migrationStateModel._databaseBackup.blobs; + if (this.migrationStateModel._didUpdateDatabasesForMigration) { + this.migrationStateModel._targetDatabaseNames = []; + this.migrationStateModel._databaseBackup.networkShares = []; + this.migrationStateModel._databaseBackup.blobs = []; + } + + this.migrationStateModel._databasesForMigration.forEach((db, index) => { + let targetDatabaseName = db; + let networkShare = {}; + let blob = {}; + + if (this.migrationStateModel._didUpdateDatabasesForMigration) { + const dbIndex = this.migrationStateModel._sourceDatabaseNames?.indexOf(db); + if (dbIndex > -1) { + targetDatabaseName = originalTargetDatabaseNames[dbIndex] ?? targetDatabaseName; + networkShare = originalNetworkShares[dbIndex] ?? networkShare; + blob = originalBlobs[dbIndex] ?? blob; + } else { + // network share values are uniform for all dbs in the same migration, except for networkShareLocation + const previouslySelectedNetworkShare = originalNetworkShares[0]; + if (previouslySelectedNetworkShare) { + networkShare = { + ...previouslySelectedNetworkShare, + networkShareLocation: '', + }; + } + } + } + this.migrationStateModel._targetDatabaseNames[index] = targetDatabaseName; + this.migrationStateModel._databaseBackup.networkShares[index] = networkShare; + this.migrationStateModel._databaseBackup.blobs[index] = blob; + const targetDatabaseInput = this._view.modelBuilder.inputBox().withProps({ required: true, - value: db, + value: targetDatabaseName, 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. @@ -830,11 +836,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { 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 { - targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index]; - } + targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index]; this._networkShareTargetDatabaseNames.push(targetDatabaseInput); const networkShareLocationInput = this._view.modelBuilder.inputBox().withProps({ @@ -856,16 +858,12 @@ export class DatabaseBackupPage extends MigrationWizardPage { this.migrationStateModel._databaseBackup.networkShares[index].networkShareLocation = value.trim(); await this.validateFields(); })); - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) { - networkShareLocationInput.value = this.migrationStateModel.savedInfo.networkShares[index].networkShareLocation; - } else { - networkShareLocationInput.value = this.migrationStateModel._databaseBackup.networkShares[index]?.networkShareLocation; - } + networkShareLocationInput.value = this.migrationStateModel._databaseBackup.networkShares[index]?.networkShareLocation; this._networkShareLocations.push(networkShareLocationInput); const blobTargetDatabaseInput = this._view.modelBuilder.inputBox().withProps({ required: true, - value: db, + value: targetDatabaseName, }).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; @@ -884,11 +882,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { 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]; - } + targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index]; this._blobContainerTargetDatabaseNames.push(blobTargetDatabaseInput); const blobContainerResourceDropdown = this._view.modelBuilder.dropDown().withProps({ @@ -970,10 +964,11 @@ export class DatabaseBackupPage extends MigrationWizardPage { this._blobContainerLastBackupFileDropdowns.push(blobContainerLastBackupFileDropdown); } }); + this.migrationStateModel._sourceDatabaseNames = this.migrationStateModel._databasesForMigration; let data: azdata.DeclarativeTableCellValue[][] = []; - this.migrationStateModel._migrationDbs.forEach((db, index) => { + this.migrationStateModel._databasesForMigration.forEach((db, index) => { const targetRow: azdata.DeclarativeTableCellValue[] = []; targetRow.push({ value: db @@ -989,7 +984,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { await this._networkShareTargetDatabaseNamesTable.setDataValues(data); data = []; - this.migrationStateModel._migrationDbs.forEach((db, index) => { + this.migrationStateModel._databasesForMigration.forEach((db, index) => { const targetRow: azdata.DeclarativeTableCellValue[] = []; targetRow.push({ value: db @@ -1037,7 +1032,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { const errors: string[] = []; switch (this.migrationStateModel._databaseBackup.networkContainerType) { - case NetworkContainerType.NETWORK_SHARE: + case NetworkContainerType.NETWORK_SHARE: { if ((this._networkShareStorageAccountResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) { errors.push(constants.INVALID_RESOURCE_GROUP_ERROR); } @@ -1045,27 +1040,29 @@ export class DatabaseBackupPage extends MigrationWizardPage { errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR); } break; - case NetworkContainerType.BLOB_CONTAINER: + } + + case NetworkContainerType.BLOB_CONTAINER: { this._blobContainerResourceGroupDropdowns.forEach((v, index) => { if (this.shouldDisplayBlobDropdownError(v, [constants.RESOURCE_GROUP_NOT_FOUND])) { - errors.push(constants.INVALID_BLOB_RESOURCE_GROUP_ERROR(this.migrationStateModel._migrationDbs[index])); + errors.push(constants.INVALID_BLOB_RESOURCE_GROUP_ERROR(this.migrationStateModel._databasesForMigration[index])); } }); this._blobContainerStorageAccountDropdowns.forEach((v, index) => { if (this.shouldDisplayBlobDropdownError(v, [constants.NO_STORAGE_ACCOUNT_FOUND, constants.SELECT_RESOURCE_GROUP_PROMPT])) { - errors.push(constants.INVALID_BLOB_STORAGE_ACCOUNT_ERROR(this.migrationStateModel._migrationDbs[index])); + errors.push(constants.INVALID_BLOB_STORAGE_ACCOUNT_ERROR(this.migrationStateModel._databasesForMigration[index])); } }); this._blobContainerDropdowns.forEach((v, index) => { if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBCONTAINERS_FOUND, constants.SELECT_STORAGE_ACCOUNT])) { - errors.push(constants.INVALID_BLOB_CONTAINER_ERROR(this.migrationStateModel._migrationDbs[index])); + errors.push(constants.INVALID_BLOB_CONTAINER_ERROR(this.migrationStateModel._databasesForMigration[index])); } }); if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) { this._blobContainerLastBackupFileDropdowns.forEach((v, index) => { if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBFILES_FOUND, constants.SELECT_BLOB_CONTAINER])) { - errors.push(constants.INVALID_BLOB_LAST_BACKUP_FILE_ERROR(this.migrationStateModel._migrationDbs[index])); + errors.push(constants.INVALID_BLOB_LAST_BACKUP_FILE_ERROR(this.migrationStateModel._databasesForMigration[index])); } }); } @@ -1082,13 +1079,16 @@ export class DatabaseBackupPage extends MigrationWizardPage { } duplicates.forEach((d) => { if (d.length > 1) { - const dupString = `${d.map(index => this.migrationStateModel._migrationDbs[index]).join(', ')}`; + const dupString = `${d.map(index => this.migrationStateModel._databasesForMigration[index]).join(', ')}`; errors.push(constants.PROVIDE_UNIQUE_CONTAINERS + dupString); } }); } - break; + } + + default: + return false; } this.migrationStateModel._targetDatabaseNames.forEach(t => { @@ -1123,7 +1123,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { break; case NetworkContainerType.NETWORK_SHARE: // All network share migrations use the same storage account - const storageAccount = this.migrationStateModel._databaseBackup.networkShares[0].storageAccount; + const storageAccount = this.migrationStateModel._databaseBackup.networkShares[0]?.storageAccount; const storageKey = (await getStorageAccountAccessKeys( this.migrationStateModel._azureAccount, this.migrationStateModel._databaseBackup.subscription, @@ -1152,16 +1152,29 @@ export class DatabaseBackupPage extends MigrationWizardPage { this.wizard.nextButton.enabled = true; this.migrationStateModel._databaseBackup.networkContainerType = containerType; - await this._blobContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none' }); - await this._networkShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' }); - await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' }); await this._targetDatabaseContainer.updateCssStyles({ 'display': 'inline' }); - this._networkTableContainer.display = (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none'; - this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none'; - //Preserving the database Names between the 2 tables. - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) { - this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames; + switch (containerType) { + case NetworkContainerType.NETWORK_SHARE: { + await this._networkShareContainer.updateCssStyles({ 'display': 'inline' }); + await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'inline' }); + await this._networkTableContainer.updateCssStyles({ 'display': 'inline' }); + + await this._blobContainer.updateCssStyles({ 'display': 'none' }); + await this._blobTableContainer.updateCssStyles({ 'display': 'none' }); + + break; + } + case NetworkContainerType.BLOB_CONTAINER: { + await this._networkShareContainer.updateCssStyles({ 'display': 'none' }); + await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' }); + await this._networkTableContainer.updateCssStyles({ 'display': 'none' }); + + await this._blobContainer.updateCssStyles({ 'display': 'inline' }); + await this._blobTableContainer.updateCssStyles({ 'display': 'inline' }); + + break; + } } await this._windowsUserAccountText.updateProperties({ @@ -1203,11 +1216,6 @@ export class DatabaseBackupPage extends MigrationWizardPage { } private async getSubscriptionValues(): Promise { - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) { - this.migrationStateModel._targetSubscription = this.migrationStateModel.savedInfo.targetSubscription; - this.migrationStateModel._targetServerInstance = this.migrationStateModel.savedInfo.targetServerInstance; - } - this._networkShareContainerSubscription.value = this.migrationStateModel._targetSubscription.name; this._networkShareContainerLocation.value = await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.location); @@ -1224,15 +1232,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { this._networkShareStorageAccountResourceGroupDropdown.loading = true; try { this._networkShareStorageAccountResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription); - if (this.hasSavedInfo(NetworkContainerType.NETWORK_SHARE, this._networkShareStorageAccountResourceGroupDropdown.values)) { - this._networkShareStorageAccountResourceGroupDropdown.values.forEach((resource, index) => { - if ((resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.networkShares[0].resourceGroup?.id?.toLowerCase()) { - selectDropDownIndex(this._networkShareStorageAccountResourceGroupDropdown, index); - } - }); - } else { - selectDropDownIndex(this._networkShareStorageAccountResourceGroupDropdown, 0); - } + selectDefaultDropdownValue(this._networkShareStorageAccountResourceGroupDropdown, this.migrationStateModel._databaseBackup?.networkShares[0]?.resourceGroup?.id, false); } catch (error) { logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingNetworkStorageResourceGroup', error); } finally { @@ -1243,13 +1243,15 @@ export class DatabaseBackupPage extends MigrationWizardPage { private async loadNetworkShareStorageDropdown(): Promise { this._networkShareContainerStorageAccountDropdown.loading = true; + this._networkShareStorageAccountResourceGroupDropdown.loading = true; try { - this._networkShareContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.networkShares[0].resourceGroup); - selectDropDownIndex(this._networkShareContainerStorageAccountDropdown, 0); + this._networkShareContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.networkShares[0]?.resourceGroup); + selectDefaultDropdownValue(this._networkShareContainerStorageAccountDropdown, this.migrationStateModel?._databaseBackup?.networkShares[0]?.storageAccount?.id, false); } catch (error) { logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingNetworkShareStorageDropdown', error); } finally { this._networkShareContainerStorageAccountDropdown.loading = false; + this._networkShareStorageAccountResourceGroupDropdown.loading = false; } } @@ -1259,15 +1261,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.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, dropDown.values)) { - dropDown.values.forEach((resource, resourceIndex) => { - if ((resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.resourceGroup?.id?.toLowerCase()) { - selectDropDownIndex(dropDown, resourceIndex); - } - }); - } else { - selectDropDownIndex(dropDown, 0); - } + selectDefaultDropdownValue(dropDown, this.migrationStateModel._databaseBackup?.blobs[index]?.resourceGroup?.id, false); }); } catch (error) { logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobResourceGroup', error); @@ -1280,15 +1274,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { 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.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerStorageAccountDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.storageAccount)) { - this._blobContainerStorageAccountDropdowns[index].values!.forEach((resource, resourceIndex) => { - if ((resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.storageAccount?.id?.toLowerCase()) { - selectDropDownIndex(this._blobContainerStorageAccountDropdowns[index], resourceIndex); - } - }); - } else { - selectDropDownIndex(this._blobContainerStorageAccountDropdowns[index], 0); - } + selectDefaultDropdownValue(this._blobContainerStorageAccountDropdowns[index], this.migrationStateModel._databaseBackup?.blobs[index]?.storageAccount?.id, false); } catch (error) { logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobStorageDropdown', error); } finally { @@ -1301,15 +1287,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { try { const blobContainerValues = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount); this._blobContainerDropdowns[index].values = blobContainerValues; - if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.blobContainer)) { - this._blobContainerDropdowns[index].values!.forEach((resource, resourceIndex) => { - if ((resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.blobContainer?.id?.toLowerCase()) { - selectDropDownIndex(this._blobContainerDropdowns[index], resourceIndex); - } - }); - } else { - selectDropDownIndex(this._blobContainerDropdowns[index], 0); - } + selectDefaultDropdownValue(this._blobContainerDropdowns[index], this.migrationStateModel._databaseBackup?.blobs[index]?.blobContainer?.id, false); } catch (error) { logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobContainers', error); } finally { @@ -1322,15 +1300,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { try { 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.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerLastBackupFileDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.lastBackupFile)) { - this._blobContainerLastBackupFileDropdowns[index].values!.forEach((resource, resourceIndex) => { - if ((resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.lastBackupFile!.toLowerCase()) { - selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], resourceIndex); - } - }); - } else { - selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], 0); - } + selectDefaultDropdownValue(this._blobContainerLastBackupFileDropdowns[index], this.migrationStateModel._databaseBackup?.blobs[index]?.lastBackupFile, false); } catch (error) { logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobLastBackupFiles', error); } finally { @@ -1362,12 +1332,4 @@ 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; - } } diff --git a/extensions/sql-migration/src/wizard/databaseSelectorPage.ts b/extensions/sql-migration/src/wizard/databaseSelectorPage.ts index 8f1a1b58e6..8570646e7f 100644 --- a/extensions/sql-migration/src/wizard/databaseSelectorPage.ts +++ b/extensions/sql-migration/src/wizard/databaseSelectorPage.ts @@ -6,11 +6,12 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { MigrationWizardPage } from '../models/migrationWizardPage'; -import { MigrationStateModel, Page, StateChangeEvent } from '../models/stateMachine'; +import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; import * as constants from '../constants/strings'; import { IconPath, IconPathHelper } from '../constants/iconPathHelper'; import { debounce } from '../api/utils'; import * as styles from '../constants/styles'; +import { selectDatabasesFromList } from '../constants/helper'; const styleLeft: azdata.CssStyles = { 'border': 'none', @@ -89,12 +90,14 @@ export class DatabaseSelectorPage extends MigrationWizardPage { } public async onPageLeave(): Promise { - const assessedDatabases = this.migrationStateModel._databaseAssessment ?? []; - const selectedDatabases = this.selectedDbs(); + const assessedDatabases = this.migrationStateModel._assessedDatabaseList ?? []; + const selectedDatabases = this.migrationStateModel._databasesForAssessment; // run assessment if + // * no prior assessment // * the prior assessment had an error or // * the assessed databases list is different from the selected databases list - this.migrationStateModel._runAssessments = !!this.migrationStateModel._assessmentResults?.assessmentError + this.migrationStateModel._runAssessments = !this.migrationStateModel._assessmentResults + || !!this.migrationStateModel._assessmentResults?.assessmentError || assessedDatabases.length === 0 || assessedDatabases.length !== selectedDatabases.length || assessedDatabases.some(db => selectedDatabases.indexOf(db) < 0); @@ -268,20 +271,8 @@ export class DatabaseSelectorPage extends MigrationWizardPage { } ).component(); - 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; - } - }); - } - await this._databaseSelectorTable.setDataValues(this._databaseTableValues); - } + this._databaseTableValues = selectDatabasesFromList(this.migrationStateModel._databasesForAssessment, this._databaseTableValues); + await this._databaseSelectorTable.setDataValues(this._databaseTableValues); await this.updateValuesOnSelection(); this._disposables.push(this._databaseSelectorTable.onDataChanged(async () => { @@ -300,7 +291,6 @@ export class DatabaseSelectorPage extends MigrationWizardPage { flex.addItem(this._dbCount, { flex: '0 0 auto' }); flex.addItem(this._databaseSelectorTable); return flex; - // insert names of databases into table } public selectedDbs(): string[] { @@ -317,8 +307,7 @@ export class DatabaseSelectorPage extends MigrationWizardPage { await this._dbCount.updateProperties({ 'value': constants.DATABASES_SELECTED(this.selectedDbs().length, this._databaseTableValues.length) }); - this.migrationStateModel._databaseAssessment = this.selectedDbs(); - this.migrationStateModel.databaseSelectorTableValues = this._databaseSelectorTable.dataValues; + this.migrationStateModel._databasesForAssessment = this.selectedDbs(); } // undo when bug #16445 is fixed diff --git a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts index 17b781f664..c16035af31 100644 --- a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts +++ b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts @@ -5,16 +5,15 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { azureResource } from 'azureResource'; import { MigrationWizardPage } from '../models/migrationWizardPage'; -import { MigrationStateModel, NetworkContainerType, Page, StateChangeEvent } from '../models/stateMachine'; +import { MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine'; import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog'; import * as constants from '../constants/strings'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; -import { getFullResourceGroupFromId, getLocationDisplayName, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlManagedInstance, SqlVMServer } from '../api/azure'; +import { getFullResourceGroupFromId, getLocationDisplayName, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData } from '../api/azure'; import { IconPathHelper } from '../constants/iconPathHelper'; import { logError, TelemetryViews } from '../telemtery'; -import { findDropDownItemIndex, selectDropDownIndex } from '../api/utils'; +import { findDropDownItemIndex, selectDefaultDropdownValue } from '../api/utils'; import * as styles from '../constants/styles'; export class IntergrationRuntimePage extends MigrationWizardPage { @@ -76,11 +75,6 @@ export class IntergrationRuntimePage extends MigrationWizardPage { } public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise { - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime)) { - this.migrationStateModel._targetSubscription = this.migrationStateModel.savedInfo.targetSubscription; - this.migrationStateModel._targetServerInstance = this.migrationStateModel.savedInfo.targetServerInstance; - } - this._subscription.value = this.migrationStateModel._targetSubscription.name; this._location.value = await getLocationDisplayName(this.migrationStateModel._targetServerInstance.location); this._dmsInfoContainer.display = (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE && this.migrationStateModel._sqlMigrationService) ? 'inline' : 'none'; @@ -183,10 +177,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage { }).component(); this._disposables.push(this._resourceGroupDropdown.onValueChanged(async (value) => { const selectedIndex = findDropDownItemIndex(this._resourceGroupDropdown, value); - this.migrationStateModel._sqlMigrationServiceResourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex).name; - if (selectedIndex > -1) { - await this.populateDms(value); + if (selectedIndex > -1 && + value !== constants.RESOURCE_GROUP_NOT_FOUND) { + this.migrationStateModel._sqlMigrationServiceResourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex).name; + } else { + this.migrationStateModel._sqlMigrationServiceResourceGroup = undefined!; } + await this.populateDms(); })); const migrationServiceDropdownLabel = this._view.modelBuilder.text().withProps({ @@ -216,8 +213,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage { text: '' }; const selectedIndex = findDropDownItemIndex(this._dmsDropdown, value); - this.migrationStateModel._sqlMigrationService = this.migrationStateModel.getMigrationService(selectedIndex); - await this.loadMigrationServiceStatus(); + if (selectedIndex > -1) { + this.migrationStateModel._sqlMigrationService = this.migrationStateModel.getMigrationService(selectedIndex); + await this.loadMigrationServiceStatus(); + } } else { this.migrationStateModel._sqlMigrationService = undefined; this._dmsInfoContainer.display = 'none'; @@ -238,7 +237,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage { this.migrationStateModel._sqlMigrationServiceResourceGroup = createdDmsResult.resourceGroup; this.migrationStateModel._sqlMigrationService = createdDmsResult.service; await this.loadResourceGroupDropdown(); - await this.populateDms(createdDmsResult.resourceGroup); + await this.populateDms(); })); const flexContainer = this._view.modelBuilder.flexContainer().withItems([ @@ -376,38 +375,26 @@ export class IntergrationRuntimePage extends MigrationWizardPage { return container; } - public async loadResourceGroupDropdown(): Promise { this._resourceGroupDropdown.loading = true; + this._dmsDropdown.loading = true; try { this._resourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription); - 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 && (resource).name.toLowerCase() === getFullResourceGroupFromId(resourceId)) { - selectDropDownIndex(this._resourceGroupDropdown, resourceIndex); - } - }); - } + const resourceGroup = (this.migrationStateModel._sqlMigrationService) + ? getFullResourceGroupFromId(this.migrationStateModel._sqlMigrationService?.id) + : undefined; + selectDefaultDropdownValue(this._resourceGroupDropdown, resourceGroup, false); } finally { this._resourceGroupDropdown.loading = false; + this._dmsDropdown.loading = false; } } - public async populateDms(resourceGroupName: string): Promise { + public async populateDms(): Promise { this._dmsDropdown.loading = true; try { - this._dmsDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetServerInstance, resourceGroupName); - 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)) { - this._dmsDropdown.values.forEach((resource, resourceIndex) => { - if ((resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase()) { - selectDropDownIndex(this._dmsDropdown, resourceIndex); - } - }); - } else { - this._dmsDropdown.value = (selectedSqlMigrationService) ? selectedSqlMigrationService : this._dmsDropdown.values[0]; - } + this._dmsDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._sqlMigrationServiceResourceGroup); + selectDefaultDropdownValue(this._dmsDropdown, this.migrationStateModel._sqlMigrationService?.id, false); } finally { this._dmsDropdown.loading = false; } diff --git a/extensions/sql-migration/src/wizard/migrationModePage.ts b/extensions/sql-migration/src/wizard/migrationModePage.ts index e87b8a77b5..aa45394cea 100644 --- a/extensions/sql-migration/src/wizard/migrationModePage.ts +++ b/extensions/sql-migration/src/wizard/migrationModePage.ts @@ -6,7 +6,7 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { MigrationWizardPage } from '../models/migrationWizardPage'; -import { MigrationMode, MigrationStateModel, Page, StateChangeEvent } from '../models/stateMachine'; +import { MigrationMode, MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; import * as constants from '../constants/strings'; import * as styles from '../constants/styles'; @@ -17,6 +17,7 @@ export class MigrationModePage extends MigrationWizardPage { constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) { super(wizard, azdata.window.createWizardPage(constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL, 'MigrationModePage'), migrationStateModel); + this.migrationStateModel._databaseBackup.migrationMode = this.migrationStateModel._databaseBackup.migrationMode || MigrationMode.ONLINE; } protected async registerContent(view: azdata.ModelView): Promise { @@ -59,7 +60,7 @@ export class MigrationModePage extends MigrationWizardPage { }); } public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise { - if (this.originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode || this.migrationStateModel.resumeAssessment) { + if (this.originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode) { this.migrationStateModel.refreshDatabaseBackupPage = true; } @@ -71,15 +72,15 @@ export class MigrationModePage extends MigrationWizardPage { } private migrationModeContainer(): azdata.FormComponent { - const buttonGroup = 'cutoverContainer'; + const buttonGroup = 'migrationMode'; const onlineButton = this._view.modelBuilder.radioButton().withProps({ label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL, name: buttonGroup, + checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE, CSSStyles: { ...styles.LABEL_CSS, }, - checked: true }).component(); const onlineDescription = this._view.modelBuilder.text().withProps({ @@ -99,6 +100,7 @@ export class MigrationModePage extends MigrationWizardPage { const offlineButton = this._view.modelBuilder.radioButton().withProps({ label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL, name: buttonGroup, + checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE, CSSStyles: { ...styles.LABEL_CSS, 'margin-top': '12px' @@ -113,16 +115,6 @@ export class MigrationModePage extends MigrationWizardPage { } }).component(); - 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; - } else { - onlineButton.checked = false; - offlineButton.checked = true; - } - } - this._disposables.push(offlineButton.onDidChangeCheckedState((e) => { if (e) { this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE; diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts index 5746901fdc..db5e8942e3 100644 --- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts +++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts @@ -7,7 +7,7 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as mssql from '../../../mssql'; import { MigrationWizardPage } from '../models/migrationWizardPage'; -import { MigrationStateModel, MigrationTargetType, Page, PerformanceDataSourceOptions, ServerAssessment, StateChangeEvent } from '../models/stateMachine'; +import { MigrationStateModel, MigrationTargetType, PerformanceDataSourceOptions, StateChangeEvent } from '../models/stateMachine'; import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog'; import { SkuRecommendationResultsDialog } from '../dialog/skuRecommendationResults/skuRecommendationResultsDialog'; import { GetAzureRecommendationDialog } from '../dialog/skuRecommendationResults/getAzureRecommendationDialog'; @@ -17,6 +17,7 @@ import { IconPath, IconPathHelper } from '../constants/iconPathHelper'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; import * as styles from '../constants/styles'; import { SkuEditParametersDialog } from '../dialog/skuRecommendationResults/skuEditParametersDialog'; +import { logError, TelemetryViews } from '../telemtery'; export interface Product { type: MigrationTargetType; @@ -153,7 +154,7 @@ export class SKURecommendationPage extends MigrationWizardPage { this._disposables.push(refreshAssessmentButton.onDidClick(async () => { await this.startCardLoading(); - await this.migrationStateModel.getSkuRecommendations(); + this.migrationStateModel._runAssessments = true; await this.constructDetails(); })); @@ -382,12 +383,7 @@ export class SKURecommendationPage extends MigrationWizardPage { } }).component(); - let serverName = ''; - if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.serverName)) { - serverName = this.migrationStateModel.serverName; - } else { - serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName; - } + let serverName = this.migrationStateModel.serverName || (await this.migrationStateModel.getSourceConnectionProfile()).serverName; let miDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLMI); let vmDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLVM); @@ -419,37 +415,31 @@ export class SKURecommendationPage extends MigrationWizardPage { } private async changeTargetType(newTargetType: string) { - if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) { - this.migrationStateModel._databaseAssessment = this.migrationStateModel.savedInfo.databaseAssessment; - } - // remove assessed databases that have been removed from the source selection list - const miDbs = this.migrationStateModel._miDbs.filter( - db => this.migrationStateModel._databaseAssessment.findIndex( - dba => dba === db) >= 0); + switch (newTargetType) { + case MigrationTargetType.SQLMI: { + const miDbs = this.migrationStateModel._miDbs.filter( + db => this.migrationStateModel._databasesForAssessment.findIndex( + dba => dba === db) >= 0); - const vmDbs = this.migrationStateModel._vmDbs.filter( - db => this.migrationStateModel._databaseAssessment.findIndex( - dba => dba === db) >= 0); + this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI; + this.migrationStateModel._targetType = MigrationTargetType.SQLMI; + this.migrationStateModel._databasesForMigration = miDbs; + break; + } - if (newTargetType === MigrationTargetType.SQLMI) { - this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI; - 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(miDbs.length, this.migrationStateModel._databaseAssessment.length); + case MigrationTargetType.SQLVM: { + const vmDbs = this.migrationStateModel._vmDbs.filter( + db => this.migrationStateModel._databasesForAssessment.findIndex( + dba => dba === db) >= 0); + + this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM; + this.migrationStateModel._targetType = MigrationTargetType.SQLVM; + this.migrationStateModel._databasesForMigration = vmDbs; + break; } - this.migrationStateModel._targetType = MigrationTargetType.SQLMI; - this.migrationStateModel._migrationDbs = miDbs; - } else { - this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM; - 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); - } - this.migrationStateModel._targetType = MigrationTargetType.SQLVM; - this.migrationStateModel._migrationDbs = vmDbs; } + + this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel._databasesForMigration.length, this.migrationStateModel._databasesForAssessment.length); this.migrationStateModel.refreshDatabaseBackupPage = true; } @@ -459,100 +449,89 @@ export class SKURecommendationPage extends MigrationWizardPage { level: azdata.window.MessageLevel.Error }; - await this._setAssessmentState(true, false); - const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName; - const errors: string[] = []; - try { - if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage) { - this.migrationStateModel._assessmentResults = this.migrationStateModel.savedInfo.serverAssessment; - } else { + + if (this.migrationStateModel._runAssessments) { + const errors: string[] = []; + await this._setAssessmentState(true, false); + try { await this.migrationStateModel.getDatabaseAssessments(MigrationTargetType.SQLMI); - } - - 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( - e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!); - } - } catch (e) { - console.log(e); - errors.push(constants.SKU_RECOMMENDATION_ASSESSMENT_UNEXPECTED_ERROR(serverName, e)); - } finally { - this.migrationStateModel._runAssessments = errors.length > 0; - if (errors.length > 0) { - this.wizard.message = { - text: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR(serverName), - description: errors.join(EOL), - level: azdata.window.MessageLevel.Error - }; - this._assessmentStatusIcon.iconPath = IconPathHelper.error; - this._igComponent.value = constants.ASSESSMENT_FAILED(serverName); - this._detailsComponent.value = constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR(serverName); - } 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); - } - } - - if (this.hasRecommendations() && this.migrationStateModel.hasRecommendedDatabaseListChanged()) { - await this.migrationStateModel.getSkuRecommendations(); - } - - if (this.hasSavedInfo()) { - if (this.migrationStateModel.savedInfo.migrationTargetType) { - this._rbg.selectedCardId = this.migrationStateModel.savedInfo.migrationTargetType; - await this.refreshCardText(); - } - - if (this.migrationStateModel.savedInfo.migrationTargetType === MigrationTargetType.SQLMI) { - this.migrationStateModel._miDbs = this.migrationStateModel.savedInfo.databaseList; - } else { - this.migrationStateModel._vmDbs = this.migrationStateModel.savedInfo.databaseList; - } - - if (this.migrationStateModel.savedInfo.skuRecommendation) { - const skuRecommendationSavedInfo = this.migrationStateModel.savedInfo.skuRecommendation; - this.migrationStateModel._skuRecommendationPerformanceDataSource = skuRecommendationSavedInfo.skuRecommendationPerformanceDataSource!; - this.migrationStateModel._skuRecommendationPerformanceLocation = skuRecommendationSavedInfo.skuRecommendationPerformanceLocation!; - - this.migrationStateModel._skuScalingFactor = skuRecommendationSavedInfo.skuScalingFactor!; - this.migrationStateModel._skuTargetPercentile = skuRecommendationSavedInfo.skuTargetPercentile!; - this.migrationStateModel._skuEnablePreview = skuRecommendationSavedInfo.skuEnablePreview!; - await this.refreshSkuParameters(); - - switch (this.migrationStateModel._skuRecommendationPerformanceDataSource) { - case PerformanceDataSourceOptions.CollectData: { - this.migrationStateModel._perfDataCollectionStartDate = skuRecommendationSavedInfo.perfDataCollectionStartDate; - - // check if collector is still running - await this.migrationStateModel.refreshPerfDataCollection(); - if (this.migrationStateModel._perfDataCollectionIsCollecting) { - // user started collecting data, and the collector is still running - const collectionStartTime = new Date(this.migrationStateModel._perfDataCollectionStartDate!); - const expectedRefreshTime = new Date(collectionStartTime.getTime() + this.migrationStateModel.refreshGetSkuRecommendationFrequency); - const timeLeft = Math.abs(new Date().getTime() - expectedRefreshTime.getTime()); - await this.migrationStateModel.startSkuTimers(this, timeLeft); - - } else { - // user started collecting data, but collector is stopped - // set stop date to some date value - this.migrationStateModel._perfDataCollectionStopDate = this.migrationStateModel._perfDataCollectionStopDate || new Date(); - await this.migrationStateModel.getSkuRecommendations(); - } - break; - } - - case PerformanceDataSourceOptions.OpenExisting: { - await this.migrationStateModel.getSkuRecommendations(); - break; - } + 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( + e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!); + } + } catch (e) { + errors.push(constants.SKU_RECOMMENDATION_ASSESSMENT_UNEXPECTED_ERROR(serverName, e)); + logError(TelemetryViews.MigrationWizardTaSkuRecommendationPage, 'SkuRecommendationUnexpectedError', e); + } finally { + this.migrationStateModel._runAssessments = errors.length > 0; + if (errors.length > 0) { + this.wizard.message = { + text: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR(serverName), + description: errors.join(EOL), + level: azdata.window.MessageLevel.Error + }; + this._assessmentStatusIcon.iconPath = IconPathHelper.error; + this._igComponent.value = constants.ASSESSMENT_FAILED(serverName); + this._detailsComponent.value = constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR(serverName); + } 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); } } + } else { + // use prior assessment results + 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); + } + + if (this.migrationStateModel.savedInfo?.migrationTargetType) { + this._rbg.selectedCardId = this.migrationStateModel._targetType; + } + + let shouldGetSkuRecommendations = false; + if (this.hasRecommendations() && this.migrationStateModel.hasRecommendedDatabaseListChanged()) { + shouldGetSkuRecommendations = true; + } + + if (this.migrationStateModel.savedInfo?.skuRecommendation) { + await this.refreshSkuParameters(); + + switch (this.migrationStateModel._skuRecommendationPerformanceDataSource) { + case PerformanceDataSourceOptions.CollectData: { + // check if collector is still running + await this.migrationStateModel.refreshPerfDataCollection(); + if (this.migrationStateModel._perfDataCollectionIsCollecting) { + // user started collecting data, and the collector is still running + const collectionStartTime = new Date(this.migrationStateModel._perfDataCollectionStartDate!); + const expectedRefreshTime = new Date(collectionStartTime.getTime() + this.migrationStateModel.refreshGetSkuRecommendationFrequency); + const timeLeft = Math.abs(new Date().getTime() - expectedRefreshTime.getTime()); + await this.migrationStateModel.startSkuTimers(this, timeLeft); + + } else { + // user started collecting data, but collector is stopped + // set stop date to some date value + this.migrationStateModel._perfDataCollectionStopDate = this.migrationStateModel._perfDataCollectionStopDate || new Date(); + shouldGetSkuRecommendations = true; + } + break; + } + + case PerformanceDataSourceOptions.OpenExisting: { + shouldGetSkuRecommendations = true; + break; + } + } + } + + if (shouldGetSkuRecommendations) { + await this.migrationStateModel.getSkuRecommendations(); } await this.refreshSkuRecommendationComponents(); @@ -582,7 +561,7 @@ export class SKURecommendationPage extends MigrationWizardPage { display = (this._rbg.selectedCardId && (!failedAssessment || this._skipAssessmentCheckbox.checked) - && this.migrationStateModel._migrationDbs.length > 0) + && this.migrationStateModel._databasesForMigration?.length > 0) ? 'inline' : 'none'; @@ -603,7 +582,7 @@ export class SKURecommendationPage extends MigrationWizardPage { if (this._rbg.selectedCardId === undefined || this._rbg.selectedCardId === '') { errors.push(constants.SELECT_TARGET_TO_CONTINUE); } - if (this.migrationStateModel._migrationDbs.length === 0) { + if (this.migrationStateModel._databasesForMigration.length === 0) { errors.push(constants.SELECT_DATABASE_TO_MIGRATE); } @@ -638,9 +617,9 @@ export class SKURecommendationPage extends MigrationWizardPage { public async refreshCardText(showLoadingIcon: boolean = true): Promise { this._rbgLoader.loading = showLoadingIcon && true; if (this._rbg.selectedCardId === MigrationTargetType.SQLMI) { - this.migrationStateModel._migrationDbs = this.migrationStateModel._miDbs; + this.migrationStateModel._databasesForMigration = this.migrationStateModel._miDbs; } else { - this.migrationStateModel._migrationDbs = this.migrationStateModel._vmDbs; + this.migrationStateModel._databasesForMigration = this.migrationStateModel._vmDbs; } const dbCount = this.migrationStateModel._assessmentResults?.databaseAssessments?.length; @@ -1188,10 +1167,6 @@ export class SKURecommendationPage extends MigrationWizardPage { await this.refreshCardText(false); } - private hasSavedInfo(): boolean { - return this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation); - } - private hasRecommendations(): boolean { return this.migrationStateModel._skuRecommendationResults?.recommendations && !this.migrationStateModel._skuRecommendationResults?.recommendationError ? true : false; } diff --git a/extensions/sql-migration/src/wizard/summaryPage.ts b/extensions/sql-migration/src/wizard/summaryPage.ts index 1c4adac906..3cdf09345e 100644 --- a/extensions/sql-migration/src/wizard/summaryPage.ts +++ b/extensions/sql-migration/src/wizard/summaryPage.ts @@ -6,14 +6,12 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { MigrationWizardPage } from '../models/migrationWizardPage'; -import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType, Page, StateChangeEvent } from '../models/stateMachine'; +import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine'; import * as constants from '../constants/strings'; import { createHeadingTextComponent, createInformationRow, createLabelTextComponent } from './wizardController'; -import { getResourceGroupFromId, Subscription } from '../api/azure'; +import { getResourceGroupFromId } from '../api/azure'; import { TargetDatabaseSummaryDialog } from '../dialog/targetDatabaseSummary/targetDatabaseSummaryDialog'; import * as styles from '../constants/styles'; -import { azureResource } from 'azureResource'; -import { Tenant } from 'azurecore'; export class SummaryPage extends MigrationWizardPage { private _view!: azdata.ModelView; @@ -47,30 +45,10 @@ export class SummaryPage extends MigrationWizardPage { } public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise { - if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.Summary) { - this.migrationStateModel._databaseBackup.networkContainerType = this.migrationStateModel.savedInfo.networkContainerType; - this.migrationStateModel._databaseBackup.networkShares = this.migrationStateModel.savedInfo.networkShares; - this.migrationStateModel._databaseBackup.subscription = this.migrationStateModel.savedInfo.targetSubscription; - this.migrationStateModel._databaseBackup.blobs = this.migrationStateModel.savedInfo.blobs; - this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames; - - this.migrationStateModel._targetType = this.migrationStateModel.savedInfo.migrationTargetType; - this.migrationStateModel._databaseAssessment = this.migrationStateModel.savedInfo.databaseAssessment; - this.migrationStateModel._migrationDbs = this.migrationStateModel.savedInfo.databaseList; - this.migrationStateModel._targetSubscription = this.migrationStateModel.savedInfo.subscription; - this.migrationStateModel._location = this.migrationStateModel.savedInfo.location; - this.migrationStateModel._resourceGroup = this.migrationStateModel.savedInfo.resourceGroup; - this.migrationStateModel._targetServerInstance = this.migrationStateModel.savedInfo.targetServerInstance; - - this.migrationStateModel.databaseSelectorTableValues = this.migrationStateModel.savedInfo.selectedDatabases; - - this.migrationStateModel._azureAccount = this.migrationStateModel.savedInfo.azureAccount; - this.migrationStateModel._azureTenant = this.migrationStateModel.savedInfo.azureTenant; - } const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel); const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink().withProps({ url: '', - label: this.migrationStateModel._migrationDbs.length.toString(), + label: this.migrationStateModel._databasesForMigration?.length.toString(), CSSStyles: { ...styles.BODY_CSS, 'margin': '0px', @@ -128,7 +106,7 @@ export class SummaryPage extends MigrationWizardPage { await createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE), createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name), - createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._sqlMigrationService?.location!), + createInformationRow(this._view, constants.LOCATION, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._sqlMigrationService?.location!)), createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._sqlMigrationService?.properties?.resourceGroup!), createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._sqlMigrationService?.name!) ] diff --git a/extensions/sql-migration/src/wizard/targetSelectionPage.ts b/extensions/sql-migration/src/wizard/targetSelectionPage.ts index 8fe89adbbb..c488a9c54e 100644 --- a/extensions/sql-migration/src/wizard/targetSelectionPage.ts +++ b/extensions/sql-migration/src/wizard/targetSelectionPage.ts @@ -7,11 +7,11 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { EOL } from 'os'; import { MigrationWizardPage } from '../models/migrationWizardPage'; -import { MigrationStateModel, MigrationTargetType, Page, StateChangeEvent } from '../models/stateMachine'; +import { MigrationStateModel, MigrationTargetType, StateChangeEvent } from '../models/stateMachine'; import * as constants from '../constants/strings'; import * as styles from '../constants/styles'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; -import { deepClone, findDropDownItemIndex, selectDropDownIndex } from '../api/utils'; +import { deepClone, findDropDownItemIndex, selectDropDownIndex, selectDefaultDropdownValue } from '../api/utils'; export class TargetSelectionPage extends MigrationWizardPage { private _view!: azdata.ModelView; @@ -27,7 +27,6 @@ export class TargetSelectionPage extends MigrationWizardPage { private _azureResourceDropdownLabel!: azdata.TextComponent; private _azureResourceDropdown!: azdata.DropDownComponent; - constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) { super(wizard, azdata.window.createWizardPage(constants.AZURE_SQL_TARGET_PAGE_TITLE), migrationStateModel); } @@ -73,19 +72,21 @@ export class TargetSelectionPage extends MigrationWizardPage { } public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise { - switch (this.migrationStateModel._targetType) { case MigrationTargetType.SQLMI: this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_MI_CARD_TEXT); this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE; + this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE; break; case MigrationTargetType.SQLVM: this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_VM_CARD_TEXT); this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE; + this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE; break; } + await this.populateResourceInstanceDropdown(); await this.populateAzureAccountsDropdown(); this.wizard.registerNavigationValidator((pageChangeInfo) => { @@ -99,22 +100,39 @@ export class TargetSelectionPage extends MigrationWizardPage { return true; } - if ((this._azureSubscriptionDropdown.value)?.displayName === constants.NO_SUBSCRIPTIONS_FOUND) { + if (!this.migrationStateModel._azureAccount) { + errors.push(constants.INVALID_ACCOUNT_ERROR); + } + + if (!this.migrationStateModel._targetSubscription || + (this._azureSubscriptionDropdown.value)?.displayName === constants.NO_SUBSCRIPTIONS_FOUND) { errors.push(constants.INVALID_SUBSCRIPTION_ERROR); } - if ((this._azureLocationDropdown.value)?.displayName === constants.NO_LOCATION_FOUND) { + if (!this.migrationStateModel._location || + (this._azureLocationDropdown.value)?.displayName === constants.NO_LOCATION_FOUND) { errors.push(constants.INVALID_LOCATION_ERROR); } - if ((this._azureResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) { + if (!this.migrationStateModel._resourceGroup || + (this._azureResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) { errors.push(constants.INVALID_RESOURCE_GROUP_ERROR); } const resourceDropdownValue = (this._azureResourceDropdown.value)?.displayName; - if (resourceDropdownValue === constants.NO_MANAGED_INSTANCE_FOUND) { - errors.push(constants.INVALID_MANAGED_INSTANCE_ERROR); - } - else if (resourceDropdownValue === constants.NO_VIRTUAL_MACHINE_FOUND) { - errors.push(constants.INVALID_VIRTUAL_MACHINE_ERROR); + switch (this.migrationStateModel._targetType) { + case MigrationTargetType.SQLMI: { + if (!this.migrationStateModel._targetServerInstance || + resourceDropdownValue === constants.NO_MANAGED_INSTANCE_FOUND) { + errors.push(constants.INVALID_MANAGED_INSTANCE_ERROR); + } + break; + } + case MigrationTargetType.SQLVM: { + if (!this.migrationStateModel._targetServerInstance || + resourceDropdownValue === constants.NO_VIRTUAL_MACHINE_FOUND) { + errors.push(constants.INVALID_VIRTUAL_MACHINE_ERROR); + } + break; + } } if (errors.length > 0) { @@ -175,8 +193,10 @@ export class TargetSelectionPage extends MigrationWizardPage { }); } await this._azureAccountsDropdown.validate(); - await this.populateSubscriptionDropdown(); + } else { + this.migrationStateModel._azureAccount = undefined!; } + await this.populateSubscriptionDropdown(); })); const linkAccountButton = this._view.modelBuilder.hyperlink() @@ -236,11 +256,7 @@ export class TargetSelectionPage extends MigrationWizardPage { this.migrationStateModel._azureTenant = deepClone(selectedTenant); if (selectedIndex > -1) { this.migrationStateModel._azureAccount.properties.tenants = [this.migrationStateModel.getTenant(selectedIndex)]; - this.migrationStateModel._subscriptions = undefined!; - this.migrationStateModel._targetSubscription = undefined!; - this.migrationStateModel._databaseBackup.subscription = undefined!; } - })); this._accountTenantFlexContainer = this._view.modelBuilder.flexContainer() @@ -285,10 +301,12 @@ export class TargetSelectionPage extends MigrationWizardPage { if (selectedIndex > -1 && value !== constants.NO_SUBSCRIPTIONS_FOUND) { this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(selectedIndex); - this.migrationStateModel._targetServerInstance = undefined!; - this.migrationStateModel._sqlMigrationService = undefined!; - await this.populateLocationDropdown(); + } else { + this.migrationStateModel._targetSubscription = undefined!; } + this.migrationStateModel.refreshDatabaseBackupPage = true; + await this.populateLocationDropdown(); + await this.populateResourceGroupDropdown(); })); const azureLocationLabel = this._view.modelBuilder.text().withProps({ @@ -315,8 +333,11 @@ export class TargetSelectionPage extends MigrationWizardPage { if (selectedIndex > -1 && value !== constants.NO_LOCATION_FOUND) { this.migrationStateModel._location = this.migrationStateModel.getLocation(selectedIndex); - await this.populateResourceGroupDropdown(); + } else { + this.migrationStateModel._location = undefined!; } + this.migrationStateModel.refreshDatabaseBackupPage = true; + await this.populateResourceInstanceDropdown(); })); const azureResourceGroupLabel = this._view.modelBuilder.text().withProps({ @@ -340,12 +361,13 @@ export class TargetSelectionPage extends MigrationWizardPage { }).component(); this._disposables.push(this._azureResourceGroupDropdown.onValueChanged(async (value) => { const selectedIndex = findDropDownItemIndex(this._azureResourceGroupDropdown, value); - if (selectedIndex > -1) { - if (value !== constants.RESOURCE_GROUP_NOT_FOUND) { - this.migrationStateModel._resourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex); - } - await this.populateResourceInstanceDropdown(); + if (selectedIndex > -1 && + value !== constants.RESOURCE_GROUP_NOT_FOUND) { + this.migrationStateModel._resourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex); + } else { + this.migrationStateModel._resourceGroup = undefined!; } + await this.populateResourceInstanceDropdown(); })); this._azureResourceDropdownLabel = this._view.modelBuilder.text().withProps({ @@ -367,7 +389,7 @@ export class TargetSelectionPage extends MigrationWizardPage { 'margin-top': '-1em' }, }).component(); - this._disposables.push(this._azureResourceDropdown.onValueChanged(value => { + this._disposables.push(this._azureResourceDropdown.onValueChanged(async (value) => { const selectedIndex = findDropDownItemIndex(this._azureResourceDropdown, value); if (selectedIndex > -1 && value !== constants.NO_MANAGED_INSTANCE_FOUND && @@ -383,6 +405,8 @@ export class TargetSelectionPage extends MigrationWizardPage { this.migrationStateModel._targetServerInstance = this.migrationStateModel.getManagedInstance(selectedIndex); break; } + } else { + this.migrationStateModel._targetServerInstance = undefined!; } })); @@ -404,145 +428,91 @@ export class TargetSelectionPage extends MigrationWizardPage { private async populateAzureAccountsDropdown(): Promise { try { - this._azureAccountsDropdown.loading = true; - this._azureSubscriptionDropdown.loading = true; - this._azureLocationDropdown.loading = true; - this._azureResourceGroupDropdown.loading = true; - this._azureResourceDropdown.loading = true; - + this.updateDropdownLoadingStatus(TargetDropDowns.AzureAccount, true); this._azureAccountsDropdown.values = await this.migrationStateModel.getAccountValues(); - - if (this.hasSavedInfo() && this._azureAccountsDropdown.values) { - (this._azureAccountsDropdown.values)?.forEach((account, index) => { - if ((account).name.toLowerCase() === this.migrationStateModel.savedInfo.azureAccount?.displayInfo.userId.toLowerCase()) { - selectDropDownIndex(this._azureAccountsDropdown, index); - } - }); - } else { - selectDropDownIndex(this._azureAccountsDropdown, 0); - } + selectDefaultDropdownValue(this._azureAccountsDropdown, this.migrationStateModel._azureAccount?.displayInfo?.userId, false); } finally { - this._azureAccountsDropdown.loading = false; - this._azureSubscriptionDropdown.loading = false; - this._azureLocationDropdown.loading = false; - this._azureResourceGroupDropdown.loading = false; - this._azureResourceDropdown.loading = false; + this.updateDropdownLoadingStatus(TargetDropDowns.AzureAccount, false); } } private async populateSubscriptionDropdown(): Promise { try { - this._azureSubscriptionDropdown.loading = true; - this._azureLocationDropdown.loading = true; - this._azureResourceGroupDropdown.loading = true; - this._azureResourceDropdown.loading = true; - + this.updateDropdownLoadingStatus(TargetDropDowns.Subscription, true); this._azureSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues(); - if (this.hasSavedInfo() && this._azureSubscriptionDropdown.values) { - this._azureSubscriptionDropdown.values!.forEach((subscription, index) => { - if ((subscription).name.toLowerCase() === this.migrationStateModel.savedInfo?.subscription?.id.toLowerCase()) { - selectDropDownIndex(this._azureSubscriptionDropdown, index); - } - }); - } else { - selectDropDownIndex(this._azureSubscriptionDropdown, 0); - } + selectDefaultDropdownValue(this._azureSubscriptionDropdown, this.migrationStateModel._targetSubscription?.id, false); } catch (e) { console.log(e); } finally { - this._azureSubscriptionDropdown.loading = false; - this._azureLocationDropdown.loading = false; - this._azureResourceGroupDropdown.loading = false; - this._azureResourceDropdown.loading = false; + this.updateDropdownLoadingStatus(TargetDropDowns.Subscription, false); } } public async populateLocationDropdown(): Promise { try { - this._azureLocationDropdown.loading = true; - this._azureResourceGroupDropdown.loading = true; - this._azureResourceDropdown.loading = true; - + this.updateDropdownLoadingStatus(TargetDropDowns.Location, true); this._azureLocationDropdown.values = await this.migrationStateModel.getAzureLocationDropdownValues(this.migrationStateModel._targetSubscription); - if (this.hasSavedInfo() && this._azureLocationDropdown.values) { - this._azureLocationDropdown.values.forEach((location, index) => { - if ((location)?.displayName.toLowerCase() === this.migrationStateModel.savedInfo?.location?.displayName.toLowerCase()) { - selectDropDownIndex(this._azureLocationDropdown, index); - } - }); - } else { - selectDropDownIndex(this._azureLocationDropdown, 0); - } + selectDefaultDropdownValue(this._azureLocationDropdown, this.migrationStateModel._location?.displayName, true); } catch (e) { console.log(e); } finally { - this._azureLocationDropdown.loading = false; - this._azureResourceGroupDropdown.loading = false; - this._azureResourceDropdown.loading = false; + this.updateDropdownLoadingStatus(TargetDropDowns.Location, false); } } public async populateResourceGroupDropdown(): Promise { try { - this._azureResourceGroupDropdown.loading = true; - this._azureResourceDropdown.loading = true; - + this.updateDropdownLoadingStatus(TargetDropDowns.ResourceGroup, true); this._azureResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription); - if (this.hasSavedInfo() && this._azureResourceGroupDropdown.values) { - this._azureResourceGroupDropdown.values.forEach((resourceGroup, index) => { - if ((resourceGroup)?.name.toLowerCase() === this.migrationStateModel.savedInfo?.resourceGroup?.id.toLowerCase()) { - selectDropDownIndex(this._azureResourceGroupDropdown, index); - } - }); - } else { - selectDropDownIndex(this._azureResourceGroupDropdown, 0); - } + selectDefaultDropdownValue(this._azureResourceGroupDropdown, this.migrationStateModel._resourceGroup?.id, false); } catch (e) { console.log(e); } finally { - this._azureResourceGroupDropdown.loading = false; - this._azureResourceDropdown.loading = false; - + this.updateDropdownLoadingStatus(TargetDropDowns.ResourceGroup, false); } } private async populateResourceInstanceDropdown(): Promise { try { - this._azureResourceDropdown.loading = true; - + this.updateDropdownLoadingStatus(TargetDropDowns.ResourceInstance, true); switch (this.migrationStateModel._targetType) { - case MigrationTargetType.SQLVM: - this._azureResourceDropdown.values = await this.migrationStateModel.getSqlVirtualMachineValues( - this.migrationStateModel._targetSubscription, - this.migrationStateModel._location, - this.migrationStateModel._resourceGroup); + case MigrationTargetType.SQLMI: { + this._azureResourceDropdown.values = await this.migrationStateModel.getManagedInstanceValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._location, this.migrationStateModel._resourceGroup); break; - - case MigrationTargetType.SQLMI: - this._azureResourceDropdown.values = await this.migrationStateModel.getManagedInstanceValues( - this.migrationStateModel._targetSubscription, - this.migrationStateModel._location, - this.migrationStateModel._resourceGroup); + } + case MigrationTargetType.SQLVM: { + this._azureResourceDropdown.values = await this.migrationStateModel.getSqlVirtualMachineValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._location, this.migrationStateModel._resourceGroup); break; + } } - - if (this.hasSavedInfo() && this._azureResourceDropdown.values) { - this._azureResourceDropdown.values.forEach((resource, index) => { - if ((resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.targetServerInstance?.name.toLowerCase()) { - selectDropDownIndex(this._azureResourceDropdown, index); - } - }); - } else { - selectDropDownIndex(this._azureResourceDropdown, 0); - } + selectDefaultDropdownValue(this._azureResourceDropdown, this.migrationStateModel._targetServerInstance?.name, true); } catch (e) { console.log(e); } finally { - this._azureResourceDropdown.loading = false; + this.updateDropdownLoadingStatus(TargetDropDowns.ResourceInstance, false); } } - private hasSavedInfo(): boolean { - return this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.TargetSelection); + private updateDropdownLoadingStatus(dropdown: TargetDropDowns, loading: boolean): void { + switch (dropdown) { + case TargetDropDowns.AzureAccount: + this._azureAccountsDropdown.loading = loading; + case TargetDropDowns.Subscription: + this._azureSubscriptionDropdown.loading = loading; + case TargetDropDowns.Location: + this._azureLocationDropdown.loading = loading; + case TargetDropDowns.ResourceGroup: + this._azureResourceGroupDropdown.loading = loading; + case TargetDropDowns.ResourceInstance: + this._azureResourceDropdown.loading = loading; + } } } + +export enum TargetDropDowns { + AzureAccount, + Subscription, + Location, + ResourceGroup, + ResourceInstance, +}