diff --git a/extensions/sql-migration/images/sqlDatabase.svg b/extensions/sql-migration/images/sqlDatabase.svg new file mode 100644 index 0000000000..01110e8f28 --- /dev/null +++ b/extensions/sql-migration/images/sqlDatabase.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/sql-migration/images/sqlDatabaseWarning.svg b/extensions/sql-migration/images/sqlDatabaseWarning.svg new file mode 100644 index 0000000000..e30729f414 --- /dev/null +++ b/extensions/sql-migration/images/sqlDatabaseWarning.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/sql-migration/images/sqlServer.svg b/extensions/sql-migration/images/sqlServer.svg new file mode 100644 index 0000000000..192ca0d058 --- /dev/null +++ b/extensions/sql-migration/images/sqlServer.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/extensions/sql-migration/src/constants/iconPathHelper.ts b/extensions/sql-migration/src/constants/iconPathHelper.ts index 418db19689..2dfb61f865 100644 --- a/extensions/sql-migration/src/constants/iconPathHelper.ts +++ b/extensions/sql-migration/src/constants/iconPathHelper.ts @@ -24,6 +24,9 @@ export class IconPathHelper { public static notStartedMigration: IconPath; public static sqlVmLogo: IconPath; public static sqlMiLogo: IconPath; + public static sqlServerLogo: IconPath; + public static sqlDatabaseLogo: IconPath; + public static sqlDatabaseWarningLogo: IconPath; public static setExtensionContext(context: vscode.ExtensionContext) { IconPathHelper.copy = { @@ -78,5 +81,17 @@ export class IconPathHelper { light: context.asAbsolutePath('images/sqlVM.svg'), dark: context.asAbsolutePath('images/sqlVM.svg') }; + IconPathHelper.sqlServerLogo = { + light: context.asAbsolutePath('images/sqlServer.svg'), + dark: context.asAbsolutePath('images/sqlServer.svg') + }; + IconPathHelper.sqlDatabaseLogo = { + light: context.asAbsolutePath('images/sqlDatabase.svg'), + dark: context.asAbsolutePath('images/sqlDatabase.svg') + }; + IconPathHelper.sqlDatabaseWarningLogo = { + light: context.asAbsolutePath('images/sqlDatabaseWarning.svg'), + dark: context.asAbsolutePath('images/sqlDatabaseWarning.svg') + }; } } diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index 1692dd2a09..7f8b3a4a21 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -274,6 +274,28 @@ export function ENTER_YOUR_SQL_CREDS(sqlServerName: string) { return localize('sql.migration.enter.your.sql.creds', "Enter the credentials for source SQL server instance ‘{0}’", sqlServerName); } export const USERNAME = localize('sql.migration.username', "Username"); + +//Assessment Dialog +export const DATABASES = localize('sql.migration.databases', "Databases"); +export const ISSUES = localize('sql.migration.issues', "Issues"); +export const SEARCH = localize('sql.migration.search', "Search"); +export const INSTANCE = localize('sql.migration.instance', "Instance"); +export const WARNINGS = localize('sql.migration.warnings', "Warnings"); +export const IMPACTED_OBJECTS = localize('sql.migration.impacted.objects', "Impacted Objects"); +export const OBJECT_DETAILS = localize('sql.migration.object.details', "Object details"); +export const TYPES_LABEL = localize('sql.migration.type.label', "Type:"); +export const NAMES_LABEL = localize('sql.migration.name.label', "Names:"); +export const DESCRIPTION = localize('sql.migration.description', "Description"); +export const RECOMMENDATION = localize('sql.migration.recommendation', "Recommendation"); +export const MORE_INFO = localize('sql.migration.more.info', "More Info"); +export const TARGET_PLATFORM = localize('sql.migration.target.platform', "Target Platform"); +export const WARNINGS_DETAILS = localize('sql.migration.warnings.details', "Warnings Details"); +export function IMPACT_OBJECT_TYPE(objectType: string): string { + return localize('sql.migration.impact.object.type', "Type: {0}", objectType); +} +export function IMPACT_OBJECT_NAME(objectName: string): string { + return localize('sql.migration.impact.object.name', "Name: {0}", objectName); +} export const AUTHENTICATION_TYPE = localize('sql.migration.authentication.type', "Authentication Type"); export const SQL_LOGIN = localize('sql.migration.sql.login', "SQL Login"); export const WINDOWS_AUTHENTICATION = localize('sql.migration.windows.auth', "Windows Authentication"); diff --git a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts index 7b9e16ec66..9ce8100f57 100644 --- a/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts +++ b/extensions/sql-migration/src/dialog/assessmentResults/sqlDatabasesTree.ts @@ -4,29 +4,47 @@ *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql'; +import { IconPath, IconPathHelper } from '../../constants/iconPathHelper'; import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine'; +import * as constants from '../../constants/strings'; const styleLeft: azdata.CssStyles = { 'border': 'none', 'text-align': 'left', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', - 'overflow': 'hidden' + 'overflow': 'hidden', }; const styleRight: azdata.CssStyles = { 'border': 'none', 'text-align': 'right', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', - 'overflow': 'hidden' + 'overflow': 'hidden', +}; + +const headerLeft: azdata.CssStyles = { + 'border': 'none', + 'text-align': 'left', + 'white-space': 'nowrap', + 'text-overflow': 'ellipsis', + 'overflow': 'hidden', + 'border-bottom': '1px solid' +}; +const headerRight: azdata.CssStyles = { + 'border': 'none', + 'text-align': 'right', + 'white-space': 'nowrap', + 'text-overflow': 'ellipsis', + 'overflow': 'hidden', + 'border-bottom': '1px solid' }; export class SqlDatabaseTree { - + private _view!: azdata.ModelView; private _instanceTable!: azdata.DeclarativeTableComponent; private _databaseTable!: azdata.DeclarativeTableComponent; private _assessmentResultsTable!: azdata.DeclarativeTableComponent; - private _impactedObjectsTable!: azdata.DeclarativeTableComponent; private _recommendation!: azdata.TextComponent; @@ -44,6 +62,9 @@ export class SqlDatabaseTree { private _selectedIssue!: SqlMigrationAssessmentResultItem; private _selectedObject!: SqlMigrationImpactedObjectInfo; + private _serverName!: string; + private _dbNames!: string[]; + constructor( private _model: MigrationStateModel, private _targetType: MigrationTargetType @@ -51,6 +72,7 @@ export class SqlDatabaseTree { } async createComponent(view: azdata.ModelView, dbs: string[]): Promise { + this._view = view; const component = view.modelBuilder.flexContainer().withLayout({ height: '100%', flexFlow: 'column' @@ -60,14 +82,15 @@ export class SqlDatabaseTree { }, }).component(); - component.addItem(this.createSearchComponent(view), { flex: '0 0 auto' }); - component.addItem(this.createInstanceComponent(view), { flex: '0 0 auto' }); - component.addItem(this.createDatabaseComponent(view, dbs), { flex: '1 1 auto', CSSStyles: { 'overflow-y': 'auto' } }); + component.addItem(this.createSearchComponent(), { flex: '0 0 auto' }); + component.addItem(this.createInstanceComponent(), { flex: '0 0 auto' }); + component.addItem(this.createDatabaseComponent(dbs), { flex: '1 1 auto', CSSStyles: { 'overflow-y': 'auto' } }); return component; } - private createDatabaseComponent(view: azdata.ModelView, dbs: string[]): azdata.DivContainer { - this._databaseTable = view.modelBuilder.declarativeTable().withProps( + private createDatabaseComponent(dbs: string[]): azdata.DivContainer { + + this._databaseTable = this._view.modelBuilder.declarativeTable().withProps( { enableRowSelection: true, width: 200, @@ -81,22 +104,21 @@ export class SqlDatabaseTree { width: 20, isReadOnly: false, showCheckAll: true, - headerCssStyles: styleLeft, - ariaLabel: 'Database Migration Check' // TODO localize + headerCssStyles: headerLeft, }, { - displayName: 'Databases', // TODO localize - valueType: azdata.DeclarativeDataType.string, + displayName: constants.DATABASES, + valueType: azdata.DeclarativeDataType.component, width: 100, isReadOnly: true, - headerCssStyles: styleLeft + headerCssStyles: headerLeft }, { - displayName: 'Issues', // Incidents + displayName: constants.ISSUES, valueType: azdata.DeclarativeDataType.string, width: 30, isReadOnly: true, - headerCssStyles: styleRight, + headerCssStyles: headerRight, } ] } @@ -106,76 +128,69 @@ export class SqlDatabaseTree { this._databaseTable.focus(); this._activeIssues = this._model._assessmentResults?.databaseAssessments[row].issues; this._selectedIssue = this._model._assessmentResults?.databaseAssessments[row].issues[0]; - this._dbName.value = this._databaseTable.dataValues![row][1].value; + this._dbName.value = this._dbNames[row]; this.refreshResults(); }); - const tableContainer = view.modelBuilder.divContainer().withItems([this._databaseTable]).withProps({ + const tableContainer = this._view.modelBuilder.divContainer().withItems([this._databaseTable]).withProps({ CSSStyles: { 'width': '200px', - 'margin-left': '15px', - 'margin-right': '5px', - 'margin-bottom': '10px' + 'margin': '0px 8px 0px 34px' } }).component(); return tableContainer; } - private createSearchComponent(view: azdata.ModelView): azdata.DivContainer { - let resourceSearchBox = view.modelBuilder.inputBox().withProperties({ - placeHolder: 'Search', + private createSearchComponent(): azdata.DivContainer { + let resourceSearchBox = this._view.modelBuilder.inputBox().withProps({ + placeHolder: constants.SEARCH, + width: 200 }).component(); - const searchContainer = view.modelBuilder.divContainer().withItems([resourceSearchBox]).withProps({ + const searchContainer = this._view.modelBuilder.divContainer().withItems([resourceSearchBox]).withProps({ CSSStyles: { 'width': '200px', - 'margin-left': '15px', - 'margin-right': '5px', - 'margin-bottom': '10px' + 'margin': '32px 8px 0px 34px' } }).component(); return searchContainer; } - private createInstanceComponent(view: azdata.ModelView): azdata.DivContainer { - this._instanceTable = view.modelBuilder.declarativeTable().withProps( + private createInstanceComponent(): azdata.DivContainer { + this._instanceTable = this._view.modelBuilder.declarativeTable().withProps( { enableRowSelection: true, width: 200, columns: [ { - displayName: 'Instance', - valueType: azdata.DeclarativeDataType.string, + displayName: constants.INSTANCE, + valueType: azdata.DeclarativeDataType.component, width: 150, isReadOnly: true, - headerCssStyles: styleLeft, - ariaLabel: 'Database Migration Check' // TODO localize + headerCssStyles: headerLeft }, { - displayName: 'Warnings', // TODO localize + displayName: constants.WARNINGS, valueType: azdata.DeclarativeDataType.string, width: 50, isReadOnly: true, - headerCssStyles: styleRight + headerCssStyles: headerRight } ], - }).component(); - const instanceContainer = view.modelBuilder.divContainer().withItems([this._instanceTable]).withProps({ + const instanceContainer = this._view.modelBuilder.divContainer().withItems([this._instanceTable]).withProps({ CSSStyles: { 'width': '200px', - 'margin-left': '15px', - 'margin-right': '5px', - 'margin-bottom': '10px' + 'margin': '19px 8px 0px 34px' } }).component(); this._instanceTable.onRowSelected((e) => { this._activeIssues = this._model._assessmentResults?.issues; this._selectedIssue = this._model._assessmentResults?.issues[0]; - this._dbName.value = this._instanceTable.dataValues![0][0].value; + this._dbName.value = this._serverName; this.refreshResults(); }); @@ -183,17 +198,16 @@ export class SqlDatabaseTree { } async createComponentResult(view: azdata.ModelView): Promise { + this._view = view; + const topContainer = this.createTopContainer(); + const bottomContainer = this.createBottomContainer(); - const topContainer = this.createTopContainer(view); - const bottomContainer = this.createBottomContainer(view); - - const container = view.modelBuilder.flexContainer().withLayout({ + const container = this._view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', height: '100%' }).withProps({ CSSStyles: { - 'margin-left': '10px', - 'margin-right': '15px', + 'margin': '32px 0px 0px 18px', 'overflow-y': 'hidden' } }).component(); @@ -205,25 +219,42 @@ export class SqlDatabaseTree { } - private createTopContainer(view: azdata.ModelView): azdata.FlexContainer { - const title = this.createTitleComponent(view); - const impact = this.createPlatformComponent(view); - const recommendation = this.createRecommendationComponent(view); - const assessmentResults = this.createAssessmentResultsTitle(view); + private createTopContainer(): azdata.FlexContainer { + const title = this.createTitleComponent(); + const impact = this.createPlatformComponent(); + const recommendation = this.createRecommendationComponent(); + const assessmentResultsTitle = this.createAssessmentResultsTitle(); + const assessmentDetailsTitle = this.createAssessmentDetailsTitle(); - const container = view.modelBuilder.flexContainer().withItems([title, impact, recommendation, assessmentResults]).withLayout({ + const titleContainer = this._view.modelBuilder.flexContainer().withItems([ + ]).withProps({ + CSSStyles: { + 'border-bottom': 'solid 1px', + 'width': '800px' + } + }).component(); + + titleContainer.addItem(assessmentResultsTitle, { + flex: '0 0 auto' + }); + + titleContainer.addItem(assessmentDetailsTitle, { + flex: '0 0 auto' + }); + + const container = this._view.modelBuilder.flexContainer().withItems([title, impact, recommendation, titleContainer]).withLayout({ flexFlow: 'column' }).component(); return container; } - private createBottomContainer(view: azdata.ModelView): azdata.FlexContainer { + private createBottomContainer(): azdata.FlexContainer { - const impactedObjects = this.createImpactedObjectsTable(view); - const rightContainer = this.createAssessmentContainer(view); + const impactedObjects = this.createImpactedObjectsTable(); + const rightContainer = this.createAssessmentContainer(); - const container = view.modelBuilder.flexContainer().withLayout({ + const container = this._view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '100%' }).withProps({ @@ -237,189 +268,196 @@ export class SqlDatabaseTree { return container; } - private createAssessmentContainer(view: azdata.ModelView): azdata.FlexContainer { - const title = this.createAssessmentTitle(view); + private createAssessmentContainer(): azdata.FlexContainer { + const title = this.createAssessmentTitle(); - const bottomContainer = this.createDescriptionContainer(view); + const bottomContainer = this.createDescriptionContainer(); - const container = view.modelBuilder.flexContainer().withItems([title, bottomContainer]).withLayout({ + const container = this._view.modelBuilder.flexContainer().withItems([title, bottomContainer]).withLayout({ flexFlow: 'column' }).withProps({ CSSStyles: { - 'margin-left': '10px' + 'margin-left': '24px' } }).component(); return container; } - private createDescriptionContainer(view: azdata.ModelView): azdata.FlexContainer { - const description = this.createDescription(view); - const impactedObjects = this.createImpactedObjectsDescription(view); + private createDescriptionContainer(): azdata.FlexContainer { + const description = this.createDescription(); + const impactedObjects = this.createImpactedObjectsDescription(); - const container = view.modelBuilder.flexContainer().withLayout({ + const container = this._view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).withProps({ CSSStyles: { 'height': '100%' } }).component(); - container.addItem(description, { flex: '1 1 auto', CSSStyles: { 'width': '50%', 'margin-right': '10px' } }); - container.addItem(impactedObjects, { flex: '1 1 auto', CSSStyles: { 'width': '50%', 'margin-left': '10px' } }); + container.addItem(description, { flex: '0 0 auto', CSSStyles: { 'width': '200px', 'margin-right': '35px' } }); + container.addItem(impactedObjects, { flex: '0 0 auto', CSSStyles: { 'width': '280px' } }); return container; } - private createImpactedObjectsDescription(view: azdata.ModelView): azdata.FlexContainer { - const impactedObjectsTitle = view.modelBuilder.text().withProperties({ - value: 'Impacted Objects', + private createImpactedObjectsDescription(): azdata.FlexContainer { + const impactedObjectsTitle = this._view.modelBuilder.text().withProps({ + value: constants.IMPACTED_OBJECTS, CSSStyles: { - 'font-size': '14px' + 'font-size': '14px', + 'width': '280px', + 'margin': '10px 0px 0px 0px' } }).component(); - const headerStyle: azdata.CssStyles = { - 'border': 'none', - 'text-align': 'left' - }; const rowStyle: azdata.CssStyles = { 'border': 'none', - 'text-align': 'left' + 'text-align': 'left', + 'border-bottom': '1px solid' }; - this._impactedObjectsTable = view.modelBuilder.declarativeTable().withProps( + this._impactedObjectsTable = this._view.modelBuilder.declarativeTable().withProps( { enableRowSelection: true, width: '100%', columns: [ { - displayName: 'Type', // TODO localize + displayName: constants.TYPE, valueType: azdata.DeclarativeDataType.string, - width: '100%', + width: '120px', isReadOnly: true, - headerCssStyles: headerStyle, + headerCssStyles: headerLeft, rowCssStyles: rowStyle }, { - displayName: 'Name', // TODO localize + displayName: constants.NAME, valueType: azdata.DeclarativeDataType.string, - width: '100%', + width: '130px', isReadOnly: true, - headerCssStyles: headerStyle, + headerCssStyles: headerLeft, rowCssStyles: rowStyle }, ], dataValues: [ [ { - value: 'Agent Job' + value: '' }, { - value: 'Process Monthly Usage' + value: '' } ] - ] + ], + CSSStyles: { + 'margin-top': '12px' + } } ).component(); - this._impactedObjectsTable.onRowSelected(({ row }) => { this._selectedObject = this._impactedObjects[row]; this.refreshImpactedObject(); }); - - - - const objectDetailsTitle = view.modelBuilder.text().withProperties({ - value: 'Object details', + const objectDetailsTitle = this._view.modelBuilder.text().withProps({ + value: constants.OBJECT_DETAILS, CSSStyles: { - 'margin-top': '10px', - 'font-size': '14px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'font-size': '13px', + 'line-size': '18px', + 'margin': '12px 0px 0px 0px' } }).component(); - this._objectDetailsType = view.modelBuilder.text().withProperties({ - value: 'Type:', + this._objectDetailsType = this._view.modelBuilder.text().withProps({ + value: constants.TYPES_LABEL, CSSStyles: { - 'font-size': '14px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'font-size': '13px', + 'line-size': '18px', + 'margin': '5px 0px 0px 0px' } }).component(); - this._objectDetailsName = view.modelBuilder.text().withProperties({ - value: 'Name:', + this._objectDetailsName = this._view.modelBuilder.text().withProps({ + value: constants.NAMES_LABEL, CSSStyles: { - 'font-size': '14px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'font-size': '13px', + 'line-size': '18px', + 'margin': '5px 0px 0px 0px' } }).component(); - this._objectDetailsSample = view.modelBuilder.text().withProperties({ - value: 'Sample', + this._objectDetailsSample = this._view.modelBuilder.text().withProps({ + value: '', CSSStyles: { - 'font-size': '14px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'font-size': '13px', + 'line-size': '18px', + 'margin': '5px 0px 0px 0px' } }).component(); - const container = view.modelBuilder.flexContainer().withItems([impactedObjectsTitle, this._impactedObjectsTable, objectDetailsTitle, this._objectDetailsType, this._objectDetailsName, this._objectDetailsSample]).withLayout({ + const container = this._view.modelBuilder.flexContainer().withItems([impactedObjectsTitle, this._impactedObjectsTable, objectDetailsTitle, this._objectDetailsType, this._objectDetailsName, this._objectDetailsSample]).withLayout({ flexFlow: 'column' }).component(); return container; } - private createDescription(view: azdata.ModelView): azdata.FlexContainer { - const descriptionTitle = view.modelBuilder.text().withProperties({ - value: 'Description', + private createDescription(): azdata.FlexContainer { + const descriptionTitle = this._view.modelBuilder.text().withProps({ + value: constants.DESCRIPTION, CSSStyles: { 'font-size': '14px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'width': '200px', + 'margin': '10px 35px 0px 0px' } }).component(); - this._descriptionText = view.modelBuilder.text().withProperties({ - value: 'It is a job step that runs a PowerShell scripts.', - CSSStyles: { - 'font-size': '12px' - } - }).component(); - - const recommendationTitle = view.modelBuilder.text().withProperties({ - value: 'Recommendation', - CSSStyles: { - 'font-size': '14px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' - } - }).component(); - this._recommendationText = view.modelBuilder.text().withProperties({ - value: '', + this._descriptionText = this._view.modelBuilder.text().withProps({ CSSStyles: { 'font-size': '12px', - 'width': '250px' + 'width': '200px', + 'margin': '3px 35px 0px 0px' } }).component(); - const moreInfo = view.modelBuilder.text().withProperties({ - value: 'More Info', + + const recommendationTitle = this._view.modelBuilder.text().withProps({ + value: constants.RECOMMENDATION, CSSStyles: { 'font-size': '14px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'width': '200px', + 'margin': '12px 35px 0px 0px' } }).component(); - this._moreInfo = view.modelBuilder.hyperlink().component(); + this._recommendationText = this._view.modelBuilder.text().withProps({ + CSSStyles: { + 'font-size': '12px', + 'width': '200px', + 'margin': '3px 35px 0px 0px' + } + }).component(); + const moreInfo = this._view.modelBuilder.text().withProps({ + value: constants.MORE_INFO, + CSSStyles: { + 'font-size': '14px', + 'width': '200px', + 'margin': '15px 35px 0px 0px' + } + }).component(); + this._moreInfo = this._view.modelBuilder.hyperlink().withProps({ + label: '', + url: '', + CSSStyles: { + 'font-size': '12px', + 'width': '200px', + 'margin': '3px 35px 0px 0px' + }, + showLinkIcon: true + }).component(); - const container = view.modelBuilder.flexContainer().withItems([descriptionTitle, this._descriptionText, recommendationTitle, this._recommendationText, moreInfo, this._moreInfo]).withLayout({ + const container = this._view.modelBuilder.flexContainer().withItems([descriptionTitle, this._descriptionText, recommendationTitle, this._recommendationText, moreInfo, this._moreInfo]).withLayout({ flexFlow: 'column' }).component(); @@ -427,13 +465,15 @@ export class SqlDatabaseTree { } - private createAssessmentTitle(view: azdata.ModelView): azdata.TextComponent { - this._assessmentTitle = view.modelBuilder.text().withProperties({ + private createAssessmentTitle(): azdata.TextComponent { + this._assessmentTitle = this._view.modelBuilder.text().withProps({ value: '', CSSStyles: { - 'font-size': '15px', - 'line-size': '19px', - 'padding-bottom': '15px', + 'font-size': '13px', + 'line-size': '18px', + 'height': '48px', + 'width': '540px', + 'font-weight': '600', 'border-bottom': 'solid 1px' } }).component(); @@ -441,57 +481,67 @@ export class SqlDatabaseTree { return this._assessmentTitle; } - private createTitleComponent(view: azdata.ModelView): azdata.TextComponent { - const title = view.modelBuilder.text().withProperties({ - value: 'Target Platform', + private createTitleComponent(): azdata.TextComponent { + const title = this._view.modelBuilder.text().withProps({ + value: constants.TARGET_PLATFORM, CSSStyles: { 'font-size': '13px', 'line-size': '19px', - 'margin-block-start': '0px', - 'margin-block-end': '2px' + 'margin': '0px 0px 0px 0px' } }); return title.component(); } - private createPlatformComponent(view: azdata.ModelView): azdata.TextComponent { - const impact = view.modelBuilder.text().withProperties({ - title: 'Platform', // TODO localize - value: (this._targetType === MigrationTargetType.SQLVM) ? 'Azure SQL Virtual Machine' : 'Azure SQL Managed Instance', + private createPlatformComponent(): azdata.TextComponent { + const impact = this._view.modelBuilder.text().withProps({ + value: (this._targetType === MigrationTargetType.SQLVM) ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE, CSSStyles: { 'font-size': '18px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'margin': '0px 0px 0px 0px' } }); return impact.component(); } - private createRecommendationComponent(view: azdata.ModelView): azdata.TextComponent { - this._dbName = view.modelBuilder.text().withProperties({ - title: 'Recommendation', // TODO localize - value: 'SQL Server 1', + private createRecommendationComponent(): azdata.TextComponent { + this._dbName = this._view.modelBuilder.text().withProps({ CSSStyles: { - 'font-size': '14px', - 'font-weight': 'bold' + 'font-size': '13px', + 'font-weight': 'bold', + 'margin': '10px 0px 0px 0px' } }).component(); return this._dbName; } - private createAssessmentResultsTitle(view: azdata.ModelView): azdata.TextComponent { - this._recommendation = view.modelBuilder.text().withProperties({ - title: 'Recommendation', // TODO localize - value: 'Warnings', + private createAssessmentResultsTitle(): azdata.TextComponent { + this._recommendation = this._view.modelBuilder.text().withProps({ + value: constants.WARNINGS, CSSStyles: { - 'font-size': '14px', - 'font-weight': 'bold', - 'border-bottom': 'solid 1px', - 'margin-block-start': '0px', - 'margin-block-end': '0px' + 'font-size': '13px', + 'line-height': '18px', + 'width': '200px', + 'font-weight': '600', + 'margin': '8px 35px 5px 0px' + } + }).component(); + + return this._recommendation; + } + + private createAssessmentDetailsTitle(): azdata.TextComponent { + this._recommendation = this._view.modelBuilder.text().withProps({ + value: constants.WARNINGS_DETAILS, + CSSStyles: { + 'font-size': '13px', + 'line-height': '18px', + 'width': '200px', + 'font-weight': '600', + 'margin': '8px 0px 5px 0px' } }).component(); @@ -499,7 +549,7 @@ export class SqlDatabaseTree { } - private createImpactedObjectsTable(view: azdata.ModelView): azdata.DeclarativeTableComponent { + private createImpactedObjectsTable(): azdata.DeclarativeTableComponent { const headerStyle: azdata.CssStyles = { 'border': 'none', @@ -511,10 +561,11 @@ export class SqlDatabaseTree { 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'width': '200px', - 'overflow': 'hidden' + 'overflow': 'hidden', + 'border-bottom': '1px solid' }; - this._assessmentResultsTable = view.modelBuilder.declarativeTable().withProps( + this._assessmentResultsTable = this._view.modelBuilder.declarativeTable().withProps( { enableRowSelection: true, width: '200px', @@ -523,25 +574,13 @@ export class SqlDatabaseTree { }, columns: [ { - displayName: '', // TODO localize + displayName: '', valueType: azdata.DeclarativeDataType.string, width: '100%', isReadOnly: true, headerCssStyles: headerStyle, rowCssStyles: rowStyle } - ], - dataValues: [ - [ - { - value: 'DB1 Assessment results' - } - ], - [ - { - value: 'DB2 Assessment results' - } - ] ] } ).component(); @@ -587,7 +626,7 @@ export class SqlDatabaseTree { this._moreInfo.url = this._selectedIssue.helpLink; this._moreInfo.label = this._selectedIssue.helpLink; this._impactedObjects = this._selectedIssue.impactedObjects; - this._recommendationText.value = this._selectedIssue.message; // Expose correct property for recommendation. + this._recommendationText.value = this._selectedIssue.message; //TODO: Expose correct property for recommendation. this._impactedObjectsTable.dataValues = this._selectedIssue.impactedObjects.map((object) => { return [ { @@ -613,8 +652,8 @@ export class SqlDatabaseTree { public refreshImpactedObject(): void { if (this._selectedObject) { - this._objectDetailsType.value = `Type: ${this._selectedObject.objectType!}`; - this._objectDetailsName.value = `Name: ${this._selectedObject.name}`; + this._objectDetailsType.value = constants.IMPACT_OBJECT_TYPE(this._selectedObject.objectType!); + this._objectDetailsName.value = constants.IMPACT_OBJECT_NAME(this._selectedObject.name); this._objectDetailsSample.value = this._selectedObject.impactDetail; } else { this._objectDetailsType.value = ``; @@ -628,15 +667,15 @@ export class SqlDatabaseTree { let instanceTableValues: azdata.DeclarativeTableCellValue[][] = []; let databaseTableValues: azdata.DeclarativeTableCellValue[][] = []; const excludedDatabases = ['master', 'msdb', 'tempdb', 'model']; - const dbList = (await azdata.connection.listDatabases(this._model.sourceConnectionId)).filter(db => !excludedDatabases.includes(db)); + this._dbNames = (await azdata.connection.listDatabases(this._model.sourceConnectionId)).filter(db => !excludedDatabases.includes(db)); const selectedDbs = (this._targetType === MigrationTargetType.SQLVM) ? this._model._vmDbs : this._model._miDbs; - const serverName = (await this._model.getSourceConnectionProfile()).serverName; + this._serverName = (await this._model.getSourceConnectionProfile()).serverName; if (this._targetType === MigrationTargetType.SQLVM || !this._model._assessmentResults) { instanceTableValues = [ [ { - value: serverName, + value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName), style: styleLeft }, { @@ -645,7 +684,7 @@ export class SqlDatabaseTree { } ] ]; - dbList.forEach((db) => { + this._dbNames.forEach((db) => { databaseTableValues.push( [ { @@ -653,7 +692,7 @@ export class SqlDatabaseTree { style: styleLeft }, { - value: db, + value: this.createIconTextCell(IconPathHelper.sqlDatabaseLogo, db), style: styleLeft }, { @@ -667,7 +706,7 @@ export class SqlDatabaseTree { instanceTableValues = [ [ { - value: serverName, + value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName), style: styleLeft }, { @@ -684,7 +723,7 @@ export class SqlDatabaseTree { style: styleLeft }, { - value: db.name, + value: this.createIconTextCell((db.issues.length === 0) ? IconPathHelper.sqlDatabaseLogo : IconPathHelper.sqlDatabaseWarningLogo, db.name), style: styleLeft }, { @@ -695,11 +734,49 @@ export class SqlDatabaseTree { ); }); } - this._dbName.value = serverName; - this._activeIssues = this._model._assessmentResults.issues; - this._selectedIssue = this._model._assessmentResults?.issues[0]; - this.refreshResults(); + this._dbName.value = this._serverName; this._instanceTable.dataValues = instanceTableValues; this._databaseTable.dataValues = databaseTableValues; + if (this._targetType === MigrationTargetType.SQLMI) { + this._activeIssues = this._model._assessmentResults.issues; + this._selectedIssue = this._model._assessmentResults?.issues[0]; + this.refreshResults(); + } + } + + private createIconTextCell(icon: IconPath, text: string): azdata.FlexContainer { + + const iconComponent = this._view.modelBuilder.image().withProps({ + iconPath: icon, + iconWidth: '16px', + iconHeight: '16px', + width: '20px', + height: '20px' + }).component(); + const textComponent = this._view.modelBuilder.text().withProps({ + value: text, + CSSStyles: { + 'margin': '0px' + } + }).component(); + + const cellContainer = this._view.modelBuilder.flexContainer().withProps({ + CSSStyles: { + 'justify-content': 'left' + } + }).component(); + cellContainer.addItem(iconComponent, { + flex: '0', + CSSStyles: { + 'width': '32px' + } + }); + cellContainer.addItem(textComponent, { + CSSStyles: { + 'width': 'auto' + } + }); + + return cellContainer; } }