diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index 914c6debb4..2d3de782d4 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -269,6 +269,10 @@ export const BACKUP_LOCATION = localize('sql.migration.backup.location', "Backup export const AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS = localize('sql.migration.azure.storage.account.to.upload.backups', "Azure Storage Account to Upload Backups"); export const SHIR = localize('sql.migration.shir', "Self-hosted Integration Runtime node"); export const TARGET_NAME = localize('sql.migration.summary.target.name', "Target Databases:"); +export const DATABASE_TO_BE_MIGRATED = localize('sql.migration.database.to.be.migrated', "Database to be migrated"); +export function COUNT_DATABASES(count: number): string { + return (count === 1) ? localize('sql.migration.count.database.single', "{0} database", count) : localize('sql.migration.count.database.multiple', "{0} databases", count); +} // Open notebook quick pick string export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pick.placeholder', "Select the operation you'd like to perform"); diff --git a/extensions/sql-migration/src/dialog/targetDatabaseSummary/targetDatabaseSummaryDialog.ts b/extensions/sql-migration/src/dialog/targetDatabaseSummary/targetDatabaseSummaryDialog.ts new file mode 100644 index 0000000000..35b08e8a5f --- /dev/null +++ b/extensions/sql-migration/src/dialog/targetDatabaseSummary/targetDatabaseSummaryDialog.ts @@ -0,0 +1,166 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; +import { MigrationStateModel, NetworkContainerType } from '../../models/stateMachine'; +import * as constants from '../../constants/strings'; + +export class TargetDatabaseSummaryDialog { + private _dialogObject!: azdata.window.Dialog; + private _view!: azdata.ModelView; + private _tableLength: number; + + constructor(private _model: MigrationStateModel) { + let dialogWidth: azdata.window.DialogWidth; + if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) { + this._tableLength = 600; + dialogWidth = 'medium'; + } else { + this._tableLength = 200; + dialogWidth = 'narrow'; + } + this._dialogObject = azdata.window.createModelViewDialog( + constants.DATABASE_TO_BE_MIGRATED, + 'TargetDatabaseSummaryDialog', + dialogWidth + ); + } + + async initialize(): Promise { + let tab = azdata.window.createTab('sql.migration.CreateResourceGroupDialog'); + await tab.registerContent(async (view: azdata.ModelView) => { + this._view = view; + + const databaseCount = this._view.modelBuilder.text().withProps({ + value: constants.COUNT_DATABASES(this._model._migrationDbs.length), + CSSStyles: { + 'font-size': '13px', + 'margin-bottom': '20px' + } + }).component(); + + const headerCssStyle = { + 'border': 'none', + 'text-align': 'left', + 'white-space': 'nowrap', + 'text-overflow': 'ellipsis', + 'overflow': 'hidden', + 'border-bottom': '1px solid' + }; + + const rowCssStyle = { + 'border': 'none', + 'text-align': 'left', + 'white-space': 'nowrap', + 'text-overflow': 'ellipsis', + 'overflow': 'hidden', + }; + + const columnWidth = 150; + + let columns: azdata.DeclarativeTableColumn[] = [ + { + valueType: azdata.DeclarativeDataType.string, + displayName: constants.SOURCE_DATABASE, + isReadOnly: true, + width: columnWidth, + rowCssStyles: rowCssStyle, + headerCssStyles: headerCssStyle + }, + + { + valueType: azdata.DeclarativeDataType.string, + displayName: constants.TARGET_DATABASE_NAME, + isReadOnly: true, + width: columnWidth, + rowCssStyles: rowCssStyle, + headerCssStyles: headerCssStyle + } + ]; + + if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) { + columns.push({ + valueType: azdata.DeclarativeDataType.string, + displayName: constants.LOCATION, + isReadOnly: true, + width: columnWidth, + rowCssStyles: rowCssStyle, + headerCssStyles: headerCssStyle + }, { + valueType: azdata.DeclarativeDataType.string, + displayName: constants.RESOURCE_GROUP, + isReadOnly: true, + width: columnWidth, + rowCssStyles: rowCssStyle, + headerCssStyles: headerCssStyle + }, { + valueType: azdata.DeclarativeDataType.string, + displayName: constants.SUMMARY_AZURE_STORAGE, + isReadOnly: true, + width: columnWidth, + rowCssStyles: rowCssStyle, + headerCssStyles: headerCssStyle + }, { + valueType: azdata.DeclarativeDataType.string, + displayName: constants.BLOB_CONTAINER, + isReadOnly: true, + width: columnWidth, + rowCssStyles: rowCssStyle, + headerCssStyles: headerCssStyle + }); + } + + const tableRows: azdata.DeclarativeTableCellValue[][] = []; + + this._model._migrationDbs.forEach((db, index) => { + const tableRow: azdata.DeclarativeTableCellValue[] = []; + tableRow.push({ + value: db + }, { + value: this._model._targetDatabaseNames[index] + }); + if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) { + tableRow.push({ + value: this._model._databaseBackup.blobs[index].storageAccount.location + }, { + value: this._model._databaseBackup.blobs[index].storageAccount.resourceGroup! + }, { + value: this._model._databaseBackup.blobs[index].storageAccount.name + }, { + value: this._model._databaseBackup.blobs[index].blobContainer.name + }); + } + tableRows.push(tableRow); + }); + + const databaseTable: azdata.DeclarativeTableComponent = this._view.modelBuilder.declarativeTable().withProps({ + columns: columns, + dataValues: tableRows, + width: this._tableLength + }).component(); + + const container = this._view.modelBuilder.flexContainer().withLayout({ + flexFlow: 'column', + }).withItems([ + databaseCount, + databaseTable + ]).component(); + const formBuilder = this._view.modelBuilder.formContainer().withFormItems( + [ + { + component: container + } + ], + { + horizontal: false + } + ); + const form = formBuilder.withLayout({ width: '100%' }).component(); + return view.initializeModel(form); + }); + this._dialogObject.content = [tab]; + azdata.window.openDialog(this._dialogObject); + } +} diff --git a/extensions/sql-migration/src/wizard/summaryPage.ts b/extensions/sql-migration/src/wizard/summaryPage.ts index 65a931291a..a5f4ec9204 100644 --- a/extensions/sql-migration/src/wizard/summaryPage.ts +++ b/extensions/sql-migration/src/wizard/summaryPage.ts @@ -7,8 +7,9 @@ import * as azdata from 'azdata'; import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine'; import * as constants from '../constants/strings'; -import { createHeadingTextComponent, createInformationRow } from './wizardController'; +import { createHeadingTextComponent, createInformationRow, createLabelTextComponent } from './wizardController'; import { getResourceGroupFromId } from '../api/azure'; +import { TargetDatabaseSummaryDialog } from '../dialog/targetDatabaseSummary/targetDatabaseSummaryDialog'; export class SummaryPage extends MigrationWizardPage { private _view!: azdata.ModelView; @@ -35,13 +36,54 @@ export class SummaryPage extends MigrationWizardPage { } public async onPageEnter(): Promise { + const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel); + const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink().withProps({ + url: '', + label: this.migrationStateModel._migrationDbs.length.toString(), + CSSStyles: { + 'margin': '0px', + 'width': '300px', + 'font-size': '13px', + 'line-height': '24px' + } + }).component(); + + targetDatabaseHyperlink.onDidClick(e => { + targetDatabaseSummary.initialize(); + }); + + const targetDatabaseRow = this._view.modelBuilder.flexContainer() + .withLayout( + { + flexFlow: 'row', + alignItems: 'center', + }) + .withItems( + [ + createLabelTextComponent(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL, + { + 'margin': '0px', + 'width': '300px', + 'font-size': '13px', + 'line-height': '24px' + } + ), + targetDatabaseHyperlink + ], + { + CSSStyles: { + 'margin-right': '5px' + } + }) + .component(); + this._flexContainer.addItems( [ createHeadingTextComponent(this._view, constants.ACCOUNTS_SELECTION_PAGE_TITLE), createInformationRow(this._view, constants.ACCOUNTS_SELECTION_PAGE_TITLE, this.migrationStateModel._azureAccount.displayInfo.displayName), createHeadingTextComponent(this._view, constants.SOURCE_DATABASES), - createInformationRow(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL, this.migrationStateModel._migrationDbs.length.toString()), + targetDatabaseRow, createHeadingTextComponent(this._view, constants.SKU_RECOMMENDATION_PAGE_TITLE), createInformationRow(this._view, constants.SKU_RECOMMENDATION_PAGE_TITLE, (this.migrationStateModel._targetType === MigrationTargetType.SQLVM) ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE), @@ -107,18 +149,6 @@ export class SummaryPage extends MigrationWizardPage { ] ); } - flexContainer.addItem(createHeadingTextComponent(this._view, constants.TARGET_NAME)); - this.migrationStateModel._migrationDbs.forEach((db, index) => { - flexContainer.addItem(createInformationRow(this._view, db, this.migrationStateModel._targetDatabaseNames[index])); - if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) { - flexContainer.addItems([ - createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.blobs[index].storageAccount.location), - createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.blobs[index].storageAccount.resourceGroup!), - createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.blobs[index].storageAccount.name), - createInformationRow(this._view, constants.BLOB_CONTAINER, this.migrationStateModel._databaseBackup.blobs[index].blobContainer.name) - ]); - } - }); return flexContainer; } } diff --git a/extensions/sql-migration/src/wizard/wizardController.ts b/extensions/sql-migration/src/wizard/wizardController.ts index a0dff13bb8..8861204790 100644 --- a/extensions/sql-migration/src/wizard/wizardController.ts +++ b/extensions/sql-migration/src/wizard/wizardController.ts @@ -94,11 +94,27 @@ export function createInformationRow(view: azdata.ModelView, label: string, valu }) .withItems( [ - createLabelTextComponent(view, label), - createTextCompononent(view, value) + createLabelTextComponent(view, label, + { + 'margin': '0px', + 'width': '300px', + 'font-size': '13px', + 'line-height': '24px' + } + ), + createTextCompononent(view, value, + { + 'margin': '0px', + 'width': '300px', + 'font-size': '13px', + 'line-height': '24px' + } + ) ], { - CSSStyles: { 'margin-right': '5px' } + CSSStyles: { + 'margin-right': '5px' + } }) .component(); } @@ -114,13 +130,13 @@ export function createHeadingTextComponent(view: azdata.ModelView, value: string export function createLabelTextComponent(view: azdata.ModelView, value: string, styles: { [key: string]: string; } = { 'width': '300px' }): azdata.TextComponent { - const component = createTextCompononent(view, value); - component.updateCssStyles(styles); + const component = createTextCompononent(view, value, styles); return component; } -export function createTextCompononent(view: azdata.ModelView, value: string): azdata.TextComponent { +export function createTextCompononent(view: azdata.ModelView, value: string, styles: { [key: string]: string; } = { 'width': '300px' }): azdata.TextComponent { return view.modelBuilder.text().withProps({ - value: value + value: value, + CSSStyles: styles }).component(); }