diff --git a/extensions/sql-migration/src/api/utils.ts b/extensions/sql-migration/src/api/utils.ts index 3dba319026..209f689558 100644 --- a/extensions/sql-migration/src/api/utils.ts +++ b/extensions/sql-migration/src/api/utils.ts @@ -163,7 +163,11 @@ export function findDropDownItemIndex(dropDown: DropDownComponent, value: string } export function hashString(value: string): string { - return crypto.createHash('sha512').update(value).digest('hex'); + if (value?.length > 0) { + return crypto.createHash('sha512').update(value).digest('hex'); + } + + return ''; } export function debounce(delay: number): Function { diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index 36c72a0930..ea0c79bad6 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -5,6 +5,7 @@ import { AzureAccount } from 'azurecore'; import * as nls from 'vscode-nls'; +import { EOL } from 'os'; import { MigrationStatus } from '../models/migrationLocalStorage'; import { MigrationSourceAuthenticationType } from '../models/stateMachine'; const localize = nls.loadMessageBundle(); @@ -32,9 +33,21 @@ export const SKU_RECOMMENDATION_PAGE_TITLE = localize('sql.migration.wizard.sku. export const SKU_RECOMMENDATION_ALL_SUCCESSFUL = (databaseCount: number): string => { return localize('sql.migration.wizard.sku.all', "Based on the assessment results, all {0} of your databases in an online state can be migrated to Azure SQL.", databaseCount); }; +export const SKU_RECOMMENDATION_ERROR = localize('sql.migration.wizard.sku.error', "An error occurred while assessing your databases."); export const SKU_RECOMMENDATION_ASSESSMENT_ERROR = (serverName: string): string => { return localize('sql.migration.wizard.sku.assessment.error', "An error occurred while assessing the server '{0}'.", serverName); }; +export const SKU_RECOMMENDATION_ASSESSMENT_UNEXPECTED_ERROR = (serverName: string, error: Error): string => { + return localize( + 'sql.migration.wizard.sku.assessment.unexpected.error', + "An unexpected error occurred while assessing the server '{0}'.{3}Message: {1}{3}stack: {2}", + serverName, + error.message, + error.stack, + EOL); +}; +export const SKU_RECOMMENDATION_ASSESSMENT_ERROR_BYPASS = localize('sql.migration.wizard.sku.assessment.error.bypass', 'Check this option to skip assessment and continue the migration.'); +export const SKU_RECOMMENDATION_ASSESSMENT_ERROR_DETAIL = localize('sql.migration.wizard.sku.assessment.error.detail', '[There are no assessment results to validate readiness of your database migration. By checking this box, you acknowledge you want to proceed migrating your database to the desired Azure SQL target.]',); export const REFRESH_ASSESSMENT_BUTTON_LABEL = localize('sql.migration.refresh.assessment.button.label', "Refresh assessment"); export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose your Azure SQL target"); export const SKU_RECOMMENDATION_SUBSCRIPTION_INFO = localize('sql.migration.sku.subscription', "Subscription name for your Azure SQL target"); @@ -56,6 +69,9 @@ export const SELECT_DATABASE_TO_MIGRATE = localize('sql.migration.select.databas export const ASSESSMENT_COMPLETED = (serverName: string): string => { return localize('sql.migration.generic.congratulations', "We have completed the assessment of your SQL Server instance '{0}'.", serverName); }; +export const ASSESSMENT_FAILED = (serverName: string): string => { + return localize('sql.migration.asessment.failed', "The assessment of your SQL Server instance '{0}' failed.", serverName); +}; export function ASSESSMENT_TILE(serverName: string): string { return localize('sql.migration.assessment', "Assessment results for '{0}'", serverName); } @@ -496,6 +512,8 @@ export const ISSUES_DETAILS = localize('sql.migration.issues.details', "Issue de export const SELECT_DB_PROMPT = localize('sql.migration.select.prompt', "Click on SQL Server instance or any of the databases on the left to view its details."); export const NO_ISSUES_FOUND_VM = localize('sql.migration.no.issues.vm', "No issues found for migrating to SQL Server on Azure Virtual Machine."); export const NO_ISSUES_FOUND_MI = localize('sql.migration.no.issues.mi', "No issues found for migrating to SQL Server on Azure SQL Managed Instance."); +export const NO_RESULTS_AVAILABLE = localize('sql.migration.no.results', 'Assessment results are unavailable.'); + export function IMPACT_OBJECT_TYPE(objectType?: string): string { return objectType ? localize('sql.migration.impact.object.type', "Type: {0}", objectType) : ''; } diff --git a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts index a98b2848f8..5fa1ea1cea 100644 --- a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts +++ b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts @@ -10,6 +10,7 @@ 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'; const styleLeft: azdata.CssStyles = { 'border': 'none', @@ -45,6 +46,7 @@ const headerRight: azdata.CssStyles = { export class SqlDatabaseTree { private _view!: azdata.ModelView; + private _dialog!: azdata.window.Dialog; private _instanceTable!: azdata.DeclarativeTableComponent; private _databaseTable!: azdata.DeclarativeTableComponent; private _assessmentResultsTable!: azdata.DeclarativeTableComponent; @@ -84,6 +86,7 @@ export class SqlDatabaseTree { async createRootContainer(dialog: azdata.window.Dialog, view: azdata.ModelView): Promise { this._view = view; + this._dialog = dialog; const selectDbMessage = this.createSelectDbMessage(); this._resultComponent = await this.createComponentResult(view); @@ -388,16 +391,21 @@ export class SqlDatabaseTree { private createNoIssuesText(): azdata.FlexContainer { let message: azdata.TextComponent; + const failedAssessment = this.handleFailedAssessment(); if (this._targetType === MigrationTargetType.SQLVM) { message = this._view.modelBuilder.text().withProps({ - value: constants.NO_ISSUES_FOUND_VM, + value: failedAssessment + ? constants.NO_RESULTS_AVAILABLE + : constants.NO_ISSUES_FOUND_VM, CSSStyles: { ...styles.BODY_CSS } }).component(); } else { message = this._view.modelBuilder.text().withProps({ - value: constants.NO_ISSUES_FOUND_MI, + value: failedAssessment + ? constants.NO_RESULTS_AVAILABLE + : constants.NO_ISSUES_FOUND_MI, CSSStyles: { ...styles.BODY_CSS } @@ -415,6 +423,34 @@ export class SqlDatabaseTree { return this._noIssuesContainer; } + private handleFailedAssessment(): boolean { + const failedAssessment: boolean = this._model._assessmentResults.assessmentError !== undefined + || (this._model._assessmentResults?.errors?.length || 0) > 0; + if (failedAssessment) { + this._dialog.message = { + level: azdata.window.MessageLevel.Warning, + text: constants.ASSESSMENT_MIGRATION_WARNING, + description: this.getAssessmentError(), + }; + } + + return failedAssessment; + } + + private getAssessmentError(): string { + const errors: string[] = []; + const assessmentError = this._model._assessmentResults.assessmentError; + if (assessmentError) { + errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`); + } + if (this._model?._assessmentResults?.errors?.length! > 0) { + errors.push(...this._model._assessmentResults.errors?.map( + e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!); + } + + return errors.join(EOL); + } + private createSelectDbMessage(): azdata.FlexContainer { const message = this._view.modelBuilder.text().withProps({ value: constants.SELECT_DB_PROMPT, diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts index 83d8cfb814..b28c574ee8 100644 --- a/extensions/sql-migration/src/models/stateMachine.ts +++ b/extensions/sql-migration/src/models/stateMachine.ts @@ -231,6 +231,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId); try { const response = (await this.migrationService.getAssessments(ownerUri, this._databaseAssessment))!; + this._assessmentApiResponse = response; if (response?.assessmentResult) { response.assessmentResult.items = response.assessmentResult.items?.filter( issue => issue.appliesToMigrationTargetPlatform === targetType); @@ -238,24 +239,41 @@ export class MigrationStateModel implements Model, vscode.Disposable { response.assessmentResult.databases?.forEach( database => database.items = database.items?.filter( issue => issue.appliesToMigrationTargetPlatform === targetType)); + this._assessmentResults = { + issues: this._assessmentApiResponse?.assessmentResult?.items || [], + databaseAssessments: this._assessmentApiResponse?.assessmentResult?.databases?.map(d => { + return { + name: d.name, + issues: d.items, + errors: d.errors, + }; + }) ?? [], + errors: this._assessmentApiResponse?.errors ?? [] + }; + } else { + this._assessmentResults = { + issues: [], + databaseAssessments: this._databaseAssessment?.map(database => { + return { + name: database, + issues: [], + errors: [] + }; + }) ?? [], + errors: response?.errors ?? [], + }; } - this._assessmentApiResponse = response; - this._assessmentResults = { - issues: this._assessmentApiResponse?.assessmentResult?.items || [], - databaseAssessments: this._assessmentApiResponse?.assessmentResult?.databases?.map(d => { - return { - name: d.name, - issues: d.items, - errors: d.errors - }; - }) ?? [], - errors: this._assessmentApiResponse?.errors ?? [] - }; } catch (error) { this._assessmentResults = { issues: [], - databaseAssessments: [], + databaseAssessments: this._databaseAssessment?.map(database => { + return { + name: database, + issues: [], + errors: [] + }; + }) ?? [], errors: [], assessmentError: error }; @@ -277,7 +295,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { }); const serverAssessmentErrorsMap: Map = new Map(); - this._assessmentApiResponse.assessmentResult.errors.forEach(e => { + this._assessmentApiResponse?.assessmentResult?.errors?.forEach(e => { serverAssessmentErrorsMap.set(e.errorId, serverAssessmentErrorsMap.get(e.errorId) ?? 0 + 1); }); @@ -291,8 +309,8 @@ export class MigrationStateModel implements Model, vscode.Disposable { ); }); - const startTime = new Date(this._assessmentApiResponse.startTime); - const endTime = new Date(this._assessmentApiResponse.endedTime); + const startTime = new Date(this._assessmentApiResponse?.startTime); + const endTime = new Date(this._assessmentApiResponse?.endedTime); sendSqlMigrationActionEvent( TelemetryViews.MigrationWizardTargetSelectionPage, @@ -302,33 +320,33 @@ export class MigrationStateModel implements Model, vscode.Disposable { 'tenantId': this._azureAccount.properties.tenants[0].id, 'subscriptionId': this._targetSubscription?.id, 'resourceGroup': this._resourceGroup?.name, - 'hashedServerName': hashString(this._assessmentApiResponse.assessmentResult.name), + 'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name), 'startTime': startTime.toString(), 'endTime': endTime.toString(), - 'serverVersion': this._assessmentApiResponse.assessmentResult.serverVersion, - 'serverEdition': this._assessmentApiResponse.assessmentResult.serverEdition, - 'platform': this._assessmentApiResponse.assessmentResult.serverHostPlatform, - 'engineEdition': this._assessmentApiResponse.assessmentResult.serverEngineEdition, + 'serverVersion': this._assessmentApiResponse?.assessmentResult?.serverVersion, + 'serverEdition': this._assessmentApiResponse?.assessmentResult?.serverEdition, + 'platform': this._assessmentApiResponse?.assessmentResult?.serverHostPlatform, + 'engineEdition': this._assessmentApiResponse?.assessmentResult?.serverEngineEdition, 'serverIssues': JSON.stringify(serverIssues), - 'serverErrors': JSON.stringify(serverErrors) + 'serverErrors': JSON.stringify(serverErrors), }, { 'issuesCount': this._assessmentResults.issues.length, 'warningsCount': this._assessmentResults.databaseAssessments.reduce((count, d) => count + d.issues.length, 0), 'durationInMilliseconds': endTime.getTime() - startTime.getTime(), 'databaseCount': this._assessmentResults.databaseAssessments.length, - 'serverHostCpuCount': this._assessmentApiResponse.assessmentResult.cpuCoreCount, - 'serverHostPhysicalMemoryInBytes': this._assessmentApiResponse.assessmentResult.physicalServerMemory, - 'serverDatabases': this._assessmentApiResponse.assessmentResult.numberOfUserDatabases, - 'serverDatabasesReadyForMigration': this._assessmentApiResponse.assessmentResult.sqlManagedInstanceTargetReadiness.numberOfDatabasesReadyForMigration, - 'offlineDatabases': this._assessmentApiResponse.assessmentResult.sqlManagedInstanceTargetReadiness.numberOfNonOnlineDatabases + 'serverHostCpuCount': this._assessmentApiResponse?.assessmentResult?.cpuCoreCount, + 'serverHostPhysicalMemoryInBytes': this._assessmentApiResponse?.assessmentResult?.physicalServerMemory, + 'serverDatabases': this._assessmentApiResponse?.assessmentResult?.numberOfUserDatabases, + 'serverDatabasesReadyForMigration': this._assessmentApiResponse?.assessmentResult?.sqlManagedInstanceTargetReadiness?.numberOfDatabasesReadyForMigration, + 'offlineDatabases': this._assessmentApiResponse?.assessmentResult?.sqlManagedInstanceTargetReadiness?.numberOfNonOnlineDatabases, } ); const databaseWarningsMap: Map = new Map(); const databaseErrorsMap: Map = new Map(); - this._assessmentApiResponse.assessmentResult.databases.forEach(d => { + this._assessmentApiResponse?.assessmentResult?.databases.forEach(d => { sendSqlMigrationActionEvent( TelemetryViews.MigrationWizardTargetSelectionPage, @@ -980,7 +998,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { 'resourceGroup': this._resourceGroup?.name, 'location': this._targetServerInstance.location, 'targetType': this._targetType, - 'hashedServerName': hashString(this._assessmentApiResponse.assessmentResult.name), + 'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name), 'hashedDatabaseName': hashString(this._migrationDbs[i]), 'migrationMode': isOfflineMigration ? 'offline' : 'online', 'migrationStartTime': new Date().toString(), diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts index 0bbd18afae..d615ef456c 100644 --- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts +++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts @@ -26,6 +26,8 @@ export class SKURecommendationPage extends MigrationWizardPage { private _igComponent!: azdata.TextComponent; private _assessmentStatusIcon!: azdata.ImageComponent; private _detailsComponent!: azdata.TextComponent; + private _skipAssessmentCheckbox!: azdata.CheckBoxComponent; + private _skipAssessmentSubText!: azdata.TextComponent; private _chooseTargetComponent!: azdata.DivContainer; private _azureSubscriptionText!: azdata.TextComponent; private _managedInstanceSubscriptionDropdown!: azdata.DropDownComponent; @@ -90,6 +92,29 @@ export class SKURecommendationPage extends MigrationWizardPage { }); this._detailsComponent = this.createDetailsComponent(view); // The details of what can be moved + this._skipAssessmentCheckbox = view.modelBuilder.checkBox().withProps({ + label: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR_BYPASS, + checked: false, + CSSStyles: { + ...styles.SECTION_HEADER_CSS, + 'margin': '10px 0 0 0', + 'display': 'none' + }, + }).component(); + this._skipAssessmentSubText = view.modelBuilder.text().withProps({ + value: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR_DETAIL, + CSSStyles: { + 'margin': '0 0 0 15px', + 'font-size': '13px', + 'color': 'red', + 'width': '590px', + 'display': 'none' + }, + }).component(); + + this._disposables.push(this._skipAssessmentCheckbox.onChanged(async (value) => { + await this._setAssessmentState(false, true); + })); const refreshAssessmentButton = this._view.modelBuilder.button().withProps({ iconPath: IconPathHelper.refresh, @@ -113,6 +138,8 @@ export class SKURecommendationPage extends MigrationWizardPage { igContainer, this._detailsComponent, refreshAssessmentButton, + this._skipAssessmentCheckbox, + this._skipAssessmentSubText, ] ).withProps({ CSSStyles: { @@ -501,50 +528,49 @@ export class SKURecommendationPage extends MigrationWizardPage { text: '', level: azdata.window.MessageLevel.Error }; - await this._assessmentComponent.updateCssStyles({ display: 'block' }); - await this._formContainer.component().updateCssStyles({ display: 'none' }); - this._assessmentLoader.loading = true; + await this._setAssessmentState(true, false); + const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName; - this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName); + const errors: string[] = []; try { if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage) { this.migrationStateModel._assessmentResults = this.migrationStateModel.savedInfo.serverAssessment; } else { await this.migrationStateModel.getDatabaseAssessments(MigrationTargetType.SQLMI); } - this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults.databaseAssessments.length); - const errors: string[] = []; const assessmentError = this.migrationStateModel._assessmentResults.assessmentError; if (assessmentError) { - errors.push(`message: ${assessmentError.message} -stack: ${assessmentError.stack} -`); + 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} -errorSummary: ${e.errorSummary} -possibleCauses: ${e.possibleCauses} -guidance: ${e.guidance} -errorId: ${e.errorId} -`)!); + 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_ERROR; + } else { + this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration; + this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName); + this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults.databaseAssessments.length); } - - this.migrationStateModel._runAssessments = errors.length > 0; - } catch (e) { - console.log(e); } - if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) { + if ((this.migrationStateModel.resumeAssessment) && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) { if (this.migrationStateModel.savedInfo.migrationTargetType) { this._rbg.selectedCardId = this.migrationStateModel.savedInfo.migrationTargetType; await this.refreshCardText(); @@ -552,9 +578,39 @@ errorId: ${e.errorId} } await this.refreshCardText(); - this._assessmentLoader.loading = false; - await this._assessmentComponent.updateCssStyles({ display: 'none' }); - await this._formContainer.component().updateCssStyles({ display: 'block' }); + await this._setAssessmentState(false, this.migrationStateModel._runAssessments); + } + + private async _setAssessmentState(assessing: boolean, failedAssessment: boolean): Promise { + let display: azdata.DisplayType = assessing ? 'block' : 'none'; + await this._assessmentComponent.updateCssStyles({ 'display': display }); + this._assessmentComponent.display = display; + + display = !assessing && failedAssessment ? 'block' : 'none'; + await this._skipAssessmentCheckbox.updateCssStyles({ 'display': display }); + this._skipAssessmentCheckbox.display = display; + await this._skipAssessmentSubText.updateCssStyles({ 'display': display }); + this._skipAssessmentSubText.display = display; + + await this._formContainer.component().updateCssStyles({ 'display': !assessing ? 'block' : 'none' }); + + display = failedAssessment && !this._skipAssessmentCheckbox.checked ? 'none' : 'block'; + await this._chooseTargetComponent.updateCssStyles({ 'display': display }); + this._chooseTargetComponent.display = display; + + display = !this._rbg.selectedCardId || failedAssessment && !this._skipAssessmentCheckbox.checked ? 'none' : 'inline'; + await this.assessmentGroupContainer.updateCssStyles({ 'display': display }); + this.assessmentGroupContainer.display = display; + + display = this._rbg.selectedCardId + && (!failedAssessment || this._skipAssessmentCheckbox.checked) + && this.migrationStateModel._migrationDbs.length > 0 + ? 'inline' + : 'none'; + await this._targetContainer.updateCssStyles({ 'display': display }); + this._targetContainer.display = display; + + this._assessmentLoader.loading = assessing; } private async populateSubscriptionDropdown(): Promise { @@ -698,18 +754,9 @@ errorId: ${e.errorId} return true; }); this.wizard.nextButton.enabled = false; - if (this.migrationStateModel._runAssessments) { - await this.constructDetails(); - } - await this._assessmentComponent.updateCssStyles({ - display: 'none' - }); - await this._formContainer.component().updateCssStyles({ - display: 'block' - }); - + await this.constructDetails(); await this.populateSubscriptionDropdown(); - this.wizard.nextButton.enabled = true; + this.wizard.nextButton.enabled = this.migrationStateModel._assessmentResults !== undefined; } public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise { @@ -726,15 +773,6 @@ errorId: ${e.errorId} protected async handleStateChange(e: StateChangeEvent): Promise { } - public async refreshDatabaseCount(selectedDbs: string[]): Promise { - this.wizard.message = { - text: '', - level: azdata.window.MessageLevel.Error - }; - this.migrationStateModel._migrationDbs = selectedDbs; - await this.refreshCardText(); - } - public async refreshCardText(): Promise { this._rbgLoader.loading = true; if (this._rbg.selectedCardId === MigrationTargetType.SQLMI) { @@ -747,25 +785,16 @@ errorId: ${e.errorId} this._targetContainer.display = (this.migrationStateModel._migrationDbs.length === 0) ? 'none' : 'inline'; if (this.migrationStateModel._assessmentResults) { - const dbCount = this.migrationStateModel._assessmentResults.databaseAssessments.length; + const dbCount = this.migrationStateModel._assessmentResults.databaseAssessments?.length; + const dbWithoutIssuesCount = this.migrationStateModel._assessmentResults.databaseAssessments?.filter(db => db.issues?.length === 0).length; + this._rbg.cards[0].descriptions[1].textValue = constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount); + this._rbg.cards[1].descriptions[1].textValue = constants.CAN_BE_MIGRATED(dbCount, dbCount); - const dbWithoutIssuesCount = this.migrationStateModel._assessmentResults.databaseAssessments.filter(db => db.issues.length === 0).length; - const miCardText = constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount); - this._rbg.cards[0].descriptions[1].textValue = miCardText; - - const vmCardText = constants.CAN_BE_MIGRATED(dbCount, dbCount); - this._rbg.cards[1].descriptions[1].textValue = vmCardText; - - await this._rbg.updateProperties({ - cards: this._rbg.cards - }); + await this._rbg.updateProperties({ cards: this._rbg.cards }); } else { this._rbg.cards[0].descriptions[1].textValue = ''; this._rbg.cards[1].descriptions[1].textValue = ''; - - await this._rbg.updateProperties({ - cards: this._rbg.cards - }); + await this._rbg.updateProperties({ cards: this._rbg.cards }); } if (this._rbg.selectedCardId) {