mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
[SQL Migration] Add support for assessing XEvent session files (#22210)
* Template * Refactor * Update strings * Clean up * Add clear button * Clean up * Fix typo and use aka.ms link * Refactor to use GroupContainer * Remove dialog and clean up common strings * Fix previous/forward behavior * Make group container default to collapsed * Clean up * Slightly reword string * Add https to aka.ms link
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import { SqlDatabaseTree } from './sqlDatabasesTree';
|
||||
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
|
||||
import * as constants from '../../constants/strings';
|
||||
import * as utils from '../../api/utils';
|
||||
import { MigrationTargetType } from '../../api/utils';
|
||||
import * as fs from 'fs';
|
||||
import path = require('path');
|
||||
import { SqlMigrationImpactedObjectInfo } from '../../service/contracts';
|
||||
|
||||
export type Issues = {
|
||||
description: string,
|
||||
recommendation: string,
|
||||
moreInfo: string,
|
||||
impactedObjects: SqlMigrationImpactedObjectInfo[],
|
||||
};
|
||||
export class AssessmentResultsDialog {
|
||||
|
||||
private static readonly SelectButtonText: string = 'Select';
|
||||
private static readonly CancelButtonText: string = 'Cancel';
|
||||
|
||||
private _isOpen: boolean = false;
|
||||
private dialog: azdata.window.Dialog | undefined;
|
||||
private _model: MigrationStateModel;
|
||||
private _saveButton!: azdata.window.Button;
|
||||
private static readonly _assessmentReportName: string = 'SqlAssessmentReport.json';
|
||||
|
||||
// Dialog Name for Telemetry
|
||||
public dialogName: string | undefined;
|
||||
private _tree: SqlDatabaseTree;
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
|
||||
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private _skuRecommendationPage: SKURecommendationPage, private _targetType: MigrationTargetType) {
|
||||
this._model = model;
|
||||
this._tree = new SqlDatabaseTree(this._model, this._targetType);
|
||||
}
|
||||
|
||||
private async initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
dialog.registerContent(async (view) => {
|
||||
try {
|
||||
const flex = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}).component();
|
||||
flex.addItem(await this._tree.createRootContainer(dialog, view), { flex: '1 1 auto' });
|
||||
|
||||
this._disposables.push(view.onClosed(e => {
|
||||
this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } });
|
||||
}));
|
||||
|
||||
await view.initializeModel(flex);
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async openDialog(dialogName?: string) {
|
||||
if (!this._isOpen) {
|
||||
this._isOpen = true;
|
||||
this.dialog = azdata.window.createModelViewDialog(this.title, 'AssessmentResults', 'wide');
|
||||
|
||||
this.dialog.okButton.label = AssessmentResultsDialog.SelectButtonText;
|
||||
this.dialog.okButton.position = 'left';
|
||||
this._disposables.push(this.dialog.okButton.onClick(async () => await this.execute()));
|
||||
|
||||
this.dialog.cancelButton.label = AssessmentResultsDialog.CancelButtonText;
|
||||
this.dialog.cancelButton.position = 'left';
|
||||
this._disposables.push(this.dialog.cancelButton.onClick(async () => await this.cancel()));
|
||||
|
||||
this._saveButton = azdata.window.createButton(
|
||||
constants.SAVE_ASSESSMENT_REPORT,
|
||||
'right');
|
||||
this._disposables.push(
|
||||
this._saveButton.onClick(async () => {
|
||||
const folder = await utils.promptUserForFolder();
|
||||
if (folder) {
|
||||
const destinationFilePath = path.join(folder, AssessmentResultsDialog._assessmentReportName);
|
||||
if (this.model._assessmentReportFilePath) {
|
||||
fs.copyFile(this.model._assessmentReportFilePath, destinationFilePath, (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
void vscode.window.showInformationMessage(constants.SAVE_ASSESSMENT_REPORT_SUCCESS(destinationFilePath));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('assessment report not found');
|
||||
}
|
||||
}
|
||||
}));
|
||||
this.dialog.customButtons = [this._saveButton];
|
||||
|
||||
const dialogSetupPromises: Thenable<void>[] = [];
|
||||
|
||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
||||
|
||||
azdata.window.openDialog(this.dialog);
|
||||
|
||||
await Promise.all(dialogSetupPromises);
|
||||
|
||||
await this._tree.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
protected async execute() {
|
||||
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;
|
||||
}
|
||||
case MigrationTargetType.SQLDB: {
|
||||
this.didUpdateDatabasesForMigration(this._model._sqldbDbs, selectedDbs);
|
||||
this._model._sqldbDbs = 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;
|
||||
}
|
||||
|
||||
public get isOpen(): boolean {
|
||||
return this._isOpen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import { WizardController } from '../../wizard/wizardController';
|
||||
import * as styles from '../../constants/styles';
|
||||
import { ServiceContextChangeEvent } from '../../dashboard/tabBase';
|
||||
|
||||
export class SavedAssessmentDialog {
|
||||
|
||||
private static readonly OkButtonText: string = constants.NEXT_LABEL;
|
||||
private static readonly CancelButtonText: string = constants.CANCEL_LABEL;
|
||||
|
||||
private dialog: azdata.window.Dialog | undefined;
|
||||
private stateModel: MigrationStateModel;
|
||||
private context: vscode.ExtensionContext;
|
||||
private _serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>;
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
private _isOpen: boolean = false;
|
||||
private _rootContainer!: azdata.FlexContainer;
|
||||
|
||||
constructor(
|
||||
context: vscode.ExtensionContext,
|
||||
stateModel: MigrationStateModel,
|
||||
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>) {
|
||||
this.stateModel = stateModel;
|
||||
this.context = context;
|
||||
this._serviceContextChangedEvent = serviceContextChangedEvent;
|
||||
}
|
||||
|
||||
private async initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
dialog.registerContent(async (view) => {
|
||||
try {
|
||||
this._rootContainer = this.initializePageContent(view);
|
||||
await view.initializeModel(this._rootContainer);
|
||||
this._disposables.push(
|
||||
dialog.okButton.onClick(
|
||||
async e => await this.execute()));
|
||||
|
||||
this._disposables.push(
|
||||
dialog.cancelButton.onClick(
|
||||
e => this.cancel()));
|
||||
this._disposables.push(
|
||||
view.onClosed(
|
||||
e => this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } })));
|
||||
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async openDialog(dialogName?: string) {
|
||||
if (!this._isOpen) {
|
||||
this._isOpen = true;
|
||||
this.dialog = azdata.window.createModelViewDialog(constants.SAVED_ASSESSMENT_RESULT, constants.SAVED_ASSESSMENT_RESULT, '60%');
|
||||
this.dialog.okButton.label = SavedAssessmentDialog.OkButtonText;
|
||||
this.dialog.okButton.position = 'left';
|
||||
this.dialog.cancelButton.label = SavedAssessmentDialog.CancelButtonText;
|
||||
this.dialog.cancelButton.position = 'left';
|
||||
|
||||
const dialogSetupPromises: Thenable<void>[] = [];
|
||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
||||
azdata.window.openDialog(this.dialog);
|
||||
await Promise.all(dialogSetupPromises);
|
||||
}
|
||||
}
|
||||
|
||||
protected async execute() {
|
||||
const wizardController = new WizardController(
|
||||
this.context,
|
||||
this.stateModel,
|
||||
this._serviceContextChangedEvent);
|
||||
|
||||
await wizardController.openWizard();
|
||||
this._isOpen = false;
|
||||
}
|
||||
|
||||
protected cancel() {
|
||||
this._isOpen = false;
|
||||
}
|
||||
|
||||
public get isOpen(): boolean {
|
||||
return this._isOpen;
|
||||
}
|
||||
|
||||
public initializePageContent(view: azdata.ModelView): azdata.FlexContainer {
|
||||
const buttonGroup = 'resumeMigration';
|
||||
|
||||
const radioStart = view.modelBuilder.radioButton()
|
||||
.withProps({
|
||||
label: constants.START_NEW_SESSION,
|
||||
name: buttonGroup,
|
||||
CSSStyles: { ...styles.BODY_CSS, 'margin-bottom': '8px' },
|
||||
checked: true
|
||||
}).component();
|
||||
|
||||
this._disposables.push(
|
||||
radioStart.onDidChangeCheckedState(checked => {
|
||||
if (checked) {
|
||||
this.stateModel.resumeAssessment = false;
|
||||
}
|
||||
}));
|
||||
const radioContinue = view.modelBuilder.radioButton()
|
||||
.withProps({
|
||||
label: constants.RESUME_SESSION,
|
||||
name: buttonGroup,
|
||||
CSSStyles: { ...styles.BODY_CSS },
|
||||
checked: false
|
||||
}).component();
|
||||
|
||||
this._disposables.push(
|
||||
radioContinue.onDidChangeCheckedState(checked => {
|
||||
if (checked) {
|
||||
this.stateModel.resumeAssessment = true;
|
||||
}
|
||||
}));
|
||||
|
||||
const flex = view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column', })
|
||||
.withProps({ CSSStyles: { 'padding': '20px 15px', } })
|
||||
.component();
|
||||
flex.addItem(radioStart, { flex: '0 0 auto' });
|
||||
flex.addItem(radioContinue, { flex: '0 0 auto' });
|
||||
|
||||
return flex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,944 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { debounce, MigrationTargetType } from '../../api/utils';
|
||||
import { IconPath, IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import * as styles from '../../constants/styles';
|
||||
import { EOL } from 'os';
|
||||
import { selectDatabasesFromList } from '../../constants/helper';
|
||||
import { getSourceConnectionProfile } from '../../api/sqlUtils';
|
||||
import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../service/contracts';
|
||||
|
||||
const styleLeft: azdata.CssStyles = {
|
||||
'border': 'none',
|
||||
'text-align': 'left',
|
||||
'white-space': 'nowrap',
|
||||
'text-overflow': 'ellipsis',
|
||||
'overflow': 'hidden',
|
||||
};
|
||||
const styleRight: azdata.CssStyles = {
|
||||
'border': 'none',
|
||||
'text-align': 'right',
|
||||
'white-space': 'nowrap',
|
||||
'text-overflow': 'ellipsis',
|
||||
'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 _dialog!: azdata.window.Dialog;
|
||||
private _instanceTable!: azdata.DeclarativeTableComponent;
|
||||
private _databaseTable!: azdata.DeclarativeTableComponent;
|
||||
private _assessmentResultsList!: azdata.ListViewComponent;
|
||||
private _impactedObjectsTable!: azdata.DeclarativeTableComponent;
|
||||
private _assessmentContainer!: azdata.FlexContainer;
|
||||
private _assessmentsTable!: azdata.FlexContainer;
|
||||
private _dbMessageContainer!: azdata.FlexContainer;
|
||||
private _rootContainer!: azdata.FlexContainer;
|
||||
private _resultComponent!: azdata.Component;
|
||||
private _noIssuesContainer!: azdata.FlexContainer;
|
||||
|
||||
private _recommendation!: azdata.TextComponent;
|
||||
private _dbName!: azdata.TextComponent;
|
||||
private _recommendationText!: azdata.TextComponent;
|
||||
private _recommendationTitle!: azdata.TextComponent;
|
||||
private _descriptionText!: azdata.TextComponent;
|
||||
private _impactedObjects!: SqlMigrationImpactedObjectInfo[];
|
||||
private _objectDetailsType!: azdata.TextComponent;
|
||||
private _objectDetailsName!: azdata.TextComponent;
|
||||
private _objectDetailsSample!: azdata.TextComponent;
|
||||
private _moreInfoTitle!: azdata.TextComponent;
|
||||
private _moreInfoText!: azdata.HyperlinkComponent;
|
||||
private _assessmentTitle!: azdata.TextComponent;
|
||||
private _databaseTableValues!: azdata.DeclarativeTableCellValue[][];
|
||||
|
||||
private _activeIssues!: SqlMigrationAssessmentResultItem[];
|
||||
|
||||
private _serverName!: string;
|
||||
private _dbNames!: string[];
|
||||
private _databaseCount!: azdata.TextComponent;
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
private _model: MigrationStateModel,
|
||||
private _targetType: MigrationTargetType
|
||||
) {
|
||||
}
|
||||
|
||||
async createRootContainer(dialog: azdata.window.Dialog, view: azdata.ModelView): Promise<azdata.Component> {
|
||||
this._view = view;
|
||||
this._dialog = dialog;
|
||||
|
||||
const selectDbMessage = this.createSelectDbMessage();
|
||||
this._resultComponent = await this.createComponentResult(view);
|
||||
const treeComponent = await this.createComponent(
|
||||
view,
|
||||
(this._targetType === MigrationTargetType.SQLVM)
|
||||
? this._model._vmDbs
|
||||
: (this._targetType === MigrationTargetType.SQLMI)
|
||||
? this._model._miDbs
|
||||
: this._model._sqldbDbs);
|
||||
|
||||
this._rootContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}).component();
|
||||
this._rootContainer.addItem(treeComponent, { flex: '0 0 auto' });
|
||||
this._rootContainer.addItem(this._resultComponent, { flex: '0 0 auto' });
|
||||
this._rootContainer.addItem(selectDbMessage, { flex: '1 1 auto' });
|
||||
|
||||
if (this._targetType === MigrationTargetType.SQLMI) {
|
||||
if (this._model._assessmentResults?.databaseAssessments.some(db => db.issues.find(issue => issue.databaseRestoreFails && issue.appliesToMigrationTargetPlatform === MigrationTargetType.SQLMI))) {
|
||||
dialog.message = {
|
||||
level: azdata.window.MessageLevel.Warning,
|
||||
text: constants.ASSESSMENT_MIGRATION_WARNING_SQLMI,
|
||||
};
|
||||
}
|
||||
} else if (this._targetType === MigrationTargetType.SQLDB) {
|
||||
if (this._model._assessmentResults?.databaseAssessments.some(db => db.issues.find(issue => issue.databaseRestoreFails && issue.appliesToMigrationTargetPlatform === MigrationTargetType.SQLDB))) {
|
||||
dialog.message = {
|
||||
level: azdata.window.MessageLevel.Warning,
|
||||
text: constants.ASSESSMENT_MIGRATION_WARNING_SQLDB,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this._disposables.push(this._view.onClosed(e => {
|
||||
this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } });
|
||||
}));
|
||||
|
||||
return this._rootContainer;
|
||||
}
|
||||
|
||||
async createComponent(view: azdata.ModelView, dbs: string[]): Promise<azdata.Component> {
|
||||
this._view = view;
|
||||
const component = view.modelBuilder.flexContainer().withLayout({
|
||||
height: '100%',
|
||||
flexFlow: 'column'
|
||||
}).withProps({
|
||||
CSSStyles: {
|
||||
'border-right': 'solid 1px'
|
||||
},
|
||||
}).component();
|
||||
|
||||
component.addItem(this.createSearchComponent(), { flex: '0 0 auto' });
|
||||
component.addItem(this.createInstanceComponent(), { flex: '0 0 auto' });
|
||||
component.addItem(this.createDatabaseCount(), { flex: '0 0 auto' });
|
||||
component.addItem(this.createDatabaseComponent(dbs), { flex: '1 1 auto', CSSStyles: { 'overflow-y': 'auto' } });
|
||||
return component;
|
||||
}
|
||||
|
||||
private createDatabaseCount(): azdata.TextComponent {
|
||||
this._databaseCount = this._view.modelBuilder.text().withProps({
|
||||
CSSStyles: {
|
||||
...styles.BOLD_NOTE_CSS,
|
||||
'margin': '0px 15px 0px 15px'
|
||||
},
|
||||
value: constants.DATABASES(0, this._model._databasesForAssessment?.length)
|
||||
}).component();
|
||||
return this._databaseCount;
|
||||
}
|
||||
|
||||
private createDatabaseComponent(dbs: string[]): azdata.DivContainer {
|
||||
|
||||
this._databaseTable = this._view.modelBuilder.declarativeTable().withProps(
|
||||
{
|
||||
ariaLabel: constants.DATABASES_TABLE_TILE,
|
||||
enableRowSelection: true,
|
||||
width: 230,
|
||||
CSSStyles: {
|
||||
'table-layout': 'fixed'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.boolean,
|
||||
width: 20,
|
||||
isReadOnly: false,
|
||||
showCheckAll: true,
|
||||
headerCssStyles: headerLeft,
|
||||
},
|
||||
{
|
||||
displayName: constants.DATABASE,
|
||||
// undo when bug #16445 is fixed
|
||||
// valueType: azdata.DeclarativeDataType.component,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: 160,
|
||||
isReadOnly: true,
|
||||
headerCssStyles: headerLeft
|
||||
},
|
||||
{
|
||||
displayName: constants.ISSUES,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: 50,
|
||||
isReadOnly: true,
|
||||
headerCssStyles: headerRight,
|
||||
}
|
||||
]
|
||||
}
|
||||
).component();
|
||||
|
||||
this._disposables.push(this._databaseTable.onDataChanged(async () => {
|
||||
await this.updateValuesOnSelection();
|
||||
}));
|
||||
|
||||
this._disposables.push(this._databaseTable.onRowSelected(async (e) => {
|
||||
if (this._targetType === MigrationTargetType.SQLMI ||
|
||||
this._targetType === MigrationTargetType.SQLDB) {
|
||||
this._activeIssues = this._model._assessmentResults?.databaseAssessments[e.row].issues.filter(i => i.appliesToMigrationTargetPlatform === this._targetType);
|
||||
} else {
|
||||
this._activeIssues = [];
|
||||
}
|
||||
this._dbName.value = this._dbNames[e.row];
|
||||
this._recommendationTitle.value = constants.ISSUES_COUNT(this._activeIssues?.length);
|
||||
this._recommendation.value = constants.ISSUES_DETAILS;
|
||||
await this._resultComponent.updateCssStyles({
|
||||
'display': 'block'
|
||||
});
|
||||
await this._dbMessageContainer.updateCssStyles({
|
||||
'display': 'none'
|
||||
});
|
||||
await this.refreshResults();
|
||||
}));
|
||||
|
||||
const tableContainer = this._view.modelBuilder.divContainer().withItems([this._databaseTable]).withProps({
|
||||
width: '100%',
|
||||
CSSStyles: {
|
||||
'margin': '0px 15px 0px 15px'
|
||||
}
|
||||
}).component();
|
||||
return tableContainer;
|
||||
}
|
||||
|
||||
private createSearchComponent(): azdata.DivContainer {
|
||||
let resourceSearchBox = this._view.modelBuilder.inputBox().withProps({
|
||||
stopEnterPropagation: true,
|
||||
placeHolder: constants.SEARCH,
|
||||
width: 260
|
||||
}).component();
|
||||
|
||||
this._disposables.push(resourceSearchBox.onTextChanged(value => this._filterTableList(value)));
|
||||
|
||||
const searchContainer = this._view.modelBuilder.divContainer().withItems([resourceSearchBox]).withProps({
|
||||
CSSStyles: {
|
||||
'margin': '32px 15px 0px 15px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
return searchContainer;
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
private _filterTableList(value: string): void {
|
||||
if (this._databaseTableValues && value?.length > 0) {
|
||||
const filter: number[] = [];
|
||||
this._databaseTableValues.forEach((row, index) => {
|
||||
// undo when bug #16445 is fixed
|
||||
// const flexContainer: azdata.FlexContainer = row[1]?.value as azdata.FlexContainer;
|
||||
// const textComponent: azdata.TextComponent = flexContainer?.items[1] as azdata.TextComponent;
|
||||
// const cellText = textComponent?.value?.toLowerCase();
|
||||
const text = row[1]?.value as string;
|
||||
const cellText = text?.toLowerCase();
|
||||
const searchText: string = value?.toLowerCase();
|
||||
if (cellText?.includes(searchText)) {
|
||||
filter.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
this._databaseTable.setFilter(filter);
|
||||
} else {
|
||||
this._databaseTable.setFilter(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private createInstanceComponent(): azdata.DivContainer {
|
||||
this._instanceTable = this._view.modelBuilder.declarativeTable().withProps(
|
||||
{
|
||||
ariaLabel: constants.SQL_SERVER_INSTANCE,
|
||||
enableRowSelection: true,
|
||||
width: 240,
|
||||
CSSStyles: {
|
||||
'table-layout': 'fixed'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
displayName: constants.INSTANCE,
|
||||
// undo when bug #16445 is fixed
|
||||
// valueType: azdata.DeclarativeDataType.component,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: 190,
|
||||
isReadOnly: true,
|
||||
headerCssStyles: headerLeft
|
||||
},
|
||||
{
|
||||
displayName: constants.WARNINGS,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: 50,
|
||||
isReadOnly: true,
|
||||
headerCssStyles: headerRight
|
||||
}
|
||||
],
|
||||
}).component();
|
||||
|
||||
const instanceContainer = this._view.modelBuilder.divContainer().withItems([this._instanceTable]).withProps({
|
||||
CSSStyles: {
|
||||
'margin': '19px 15px 0px 15px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
|
||||
this._disposables.push(this._instanceTable.onRowSelected(async (e) => {
|
||||
this._activeIssues = this._model._assessmentResults?.issues.filter(issue => issue.appliesToMigrationTargetPlatform === this._targetType);
|
||||
this._dbName.value = this._serverName;
|
||||
await this._resultComponent.updateCssStyles({
|
||||
'display': 'block'
|
||||
});
|
||||
await this._dbMessageContainer.updateCssStyles({
|
||||
'display': 'none'
|
||||
});
|
||||
this._recommendation.value = constants.WARNINGS_DETAILS;
|
||||
this._recommendationTitle.value = constants.WARNINGS_COUNT(this._activeIssues?.length);
|
||||
if (this._targetType === MigrationTargetType.SQLMI ||
|
||||
this._targetType === MigrationTargetType.SQLDB) {
|
||||
await this.refreshResults();
|
||||
}
|
||||
}));
|
||||
|
||||
return instanceContainer;
|
||||
}
|
||||
|
||||
async createComponentResult(view: azdata.ModelView): Promise<azdata.Component> {
|
||||
this._view = view;
|
||||
const topContainer = this.createTopContainer();
|
||||
const bottomContainer = this.createBottomContainer();
|
||||
|
||||
const container = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
height: '100%'
|
||||
}).withProps({
|
||||
CSSStyles: {
|
||||
'margin': '32px 0px 0px 18px',
|
||||
'overflow-y': 'hidden',
|
||||
'display': 'none'
|
||||
}
|
||||
}).component();
|
||||
|
||||
container.addItem(topContainer, { flex: '0 0 auto' });
|
||||
container.addItem(bottomContainer, { flex: '1 1 auto', CSSStyles: { 'overflow-y': 'hidden' } });
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
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 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(): azdata.FlexContainer {
|
||||
|
||||
this._assessmentsTable = this.createImpactedObjectsTable();
|
||||
this._assessmentContainer = this.createAssessmentContainer();
|
||||
const noIssuesText = this.createNoIssuesText();
|
||||
|
||||
const container = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
height: '100%'
|
||||
}).withProps({
|
||||
CSSStyles: {
|
||||
'height': '100%'
|
||||
}
|
||||
}).component();
|
||||
|
||||
container.addItem(noIssuesText, { flex: '0 0 auto', CSSStyles: { 'overflow-y': 'auto' } });
|
||||
container.addItem(this._assessmentsTable, { flex: '1 1 auto', CSSStyles: { 'overflow-y': 'auto' } });
|
||||
container.addItem(this._assessmentContainer, { flex: '1 1 auto', CSSStyles: { 'overflow-y': 'auto' } });
|
||||
return container;
|
||||
}
|
||||
|
||||
private createNoIssuesText(): azdata.FlexContainer {
|
||||
const failedAssessment = this.handleFailedAssessment();
|
||||
|
||||
|
||||
const value = failedAssessment
|
||||
? constants.NO_RESULTS_AVAILABLE
|
||||
: (this._targetType === MigrationTargetType.SQLVM)
|
||||
? constants.NO_ISSUES_FOUND_VM
|
||||
: (this._targetType === MigrationTargetType.SQLMI)
|
||||
? constants.NO_ISSUES_FOUND_MI
|
||||
: constants.NO_ISSUES_FOUND_SQLDB;
|
||||
|
||||
const message = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: value,
|
||||
CSSStyles: { ...styles.BODY_CSS }
|
||||
}).component();
|
||||
|
||||
this._noIssuesContainer = this._view.modelBuilder.flexContainer()
|
||||
.withItems([message])
|
||||
.withProps({ CSSStyles: { 'margin-top': '8px', 'display': 'none' } })
|
||||
.component();
|
||||
|
||||
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,
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
'width': '400px',
|
||||
'margin': '10px 0px 0px 0px',
|
||||
'text-align': 'left'
|
||||
}
|
||||
}).component();
|
||||
this._dbMessageContainer = this._view.modelBuilder.flexContainer().withItems([message]).withProps({
|
||||
CSSStyles: {
|
||||
'margin-top': '20px',
|
||||
'margin-left': '15px',
|
||||
}
|
||||
}).component();
|
||||
|
||||
return this._dbMessageContainer;
|
||||
}
|
||||
|
||||
private createAssessmentContainer(): azdata.FlexContainer {
|
||||
const title = this.createAssessmentTitle();
|
||||
const bottomContainer = this.createDescriptionContainer();
|
||||
const container = this._view.modelBuilder.flexContainer()
|
||||
.withItems([title, bottomContainer])
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.withProps({ CSSStyles: { 'margin-left': '24px' } })
|
||||
.component();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private createDescriptionContainer(): azdata.FlexContainer {
|
||||
const description = this.createDescription();
|
||||
const impactedObjects = this.createImpactedObjectsDescription();
|
||||
const container = this._view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'row' })
|
||||
.withProps({ CSSStyles: { 'height': '100%' } })
|
||||
.component();
|
||||
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(): azdata.FlexContainer {
|
||||
const impactedObjectsTitle = this._view.modelBuilder.text().withProps({
|
||||
value: constants.IMPACTED_OBJECTS,
|
||||
CSSStyles: {
|
||||
...styles.LIGHT_LABEL_CSS,
|
||||
'width': '280px',
|
||||
'margin': '10px 0px 0px 0px',
|
||||
}
|
||||
}).component();
|
||||
|
||||
const rowStyle: azdata.CssStyles = {
|
||||
'border': 'none',
|
||||
'text-align': 'left',
|
||||
'border-bottom': '1px solid'
|
||||
};
|
||||
|
||||
this._impactedObjectsTable = this._view.modelBuilder.declarativeTable().withProps(
|
||||
{
|
||||
ariaLabel: constants.IMPACTED_OBJECTS,
|
||||
enableRowSelection: true,
|
||||
width: '100%',
|
||||
columns: [
|
||||
{
|
||||
displayName: constants.TYPE,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '120px',
|
||||
isReadOnly: true,
|
||||
headerCssStyles: headerLeft,
|
||||
rowCssStyles: rowStyle
|
||||
},
|
||||
{
|
||||
displayName: constants.NAME,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '130px',
|
||||
isReadOnly: true,
|
||||
headerCssStyles: headerLeft,
|
||||
rowCssStyles: rowStyle
|
||||
},
|
||||
],
|
||||
dataValues: [[{ value: '' }, { value: '' }]],
|
||||
CSSStyles: { 'margin-top': '12px' }
|
||||
}
|
||||
).component();
|
||||
|
||||
this._disposables.push(this._impactedObjectsTable.onRowSelected((e) => {
|
||||
const impactedObject = e.row > -1 ? this._impactedObjects[e.row] : undefined;
|
||||
this.refreshImpactedObject(impactedObject);
|
||||
}));
|
||||
|
||||
const objectDetailsTitle = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.OBJECT_DETAILS,
|
||||
CSSStyles: {
|
||||
...styles.LIGHT_LABEL_CSS,
|
||||
'margin': '12px 0px 0px 0px',
|
||||
}
|
||||
}).component();
|
||||
const objectDescriptionStyle = {
|
||||
...styles.BODY_CSS,
|
||||
'margin': '5px 0px 0px 0px',
|
||||
'word-wrap': 'break-word'
|
||||
};
|
||||
this._objectDetailsType = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.TYPES_LABEL,
|
||||
CSSStyles: objectDescriptionStyle
|
||||
}).component();
|
||||
|
||||
this._objectDetailsName = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.NAMES_LABEL,
|
||||
CSSStyles: objectDescriptionStyle
|
||||
}).component();
|
||||
|
||||
this._objectDetailsSample = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: '',
|
||||
CSSStyles: objectDescriptionStyle
|
||||
}).component();
|
||||
|
||||
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(): azdata.FlexContainer {
|
||||
const LABEL_CSS = {
|
||||
...styles.LIGHT_LABEL_CSS,
|
||||
'width': '200px',
|
||||
'margin': '12px 0 0'
|
||||
};
|
||||
const textStyle = {
|
||||
...styles.BODY_CSS,
|
||||
'width': '200px',
|
||||
'word-wrap': 'break-word'
|
||||
};
|
||||
const descriptionTitle = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.DESCRIPTION,
|
||||
CSSStyles: LABEL_CSS
|
||||
}).component();
|
||||
this._descriptionText = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
CSSStyles: textStyle
|
||||
}).component();
|
||||
|
||||
const recommendationTitle = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.RECOMMENDATION,
|
||||
CSSStyles: LABEL_CSS
|
||||
}).component();
|
||||
this._recommendationText = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
CSSStyles: textStyle
|
||||
}).component();
|
||||
|
||||
this._moreInfoTitle = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.MORE_INFO,
|
||||
CSSStyles: LABEL_CSS
|
||||
}).component();
|
||||
this._moreInfoText = this._view.modelBuilder.hyperlink()
|
||||
.withProps({
|
||||
label: '',
|
||||
url: '',
|
||||
CSSStyles: textStyle,
|
||||
ariaLabel: constants.MORE_INFO,
|
||||
showLinkIcon: true
|
||||
}).component();
|
||||
|
||||
const container = this._view.modelBuilder.flexContainer()
|
||||
.withItems([descriptionTitle,
|
||||
this._descriptionText,
|
||||
recommendationTitle,
|
||||
this._recommendationText,
|
||||
this._moreInfoTitle,
|
||||
this._moreInfoText])
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private createAssessmentTitle(): azdata.TextComponent {
|
||||
this._assessmentTitle = this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: '',
|
||||
CSSStyles: {
|
||||
...styles.LABEL_CSS,
|
||||
'margin-top': '12px',
|
||||
'height': '48px',
|
||||
'width': '540px',
|
||||
'border-bottom': 'solid 1px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
return this._assessmentTitle;
|
||||
}
|
||||
|
||||
private createTitleComponent(): azdata.TextComponent {
|
||||
return this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: constants.TARGET_PLATFORM,
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
'margin': '0 0 4px 0'
|
||||
}
|
||||
}).component();
|
||||
}
|
||||
|
||||
private createPlatformComponent(): azdata.TextComponent {
|
||||
const target = (this._targetType === MigrationTargetType.SQLVM)
|
||||
? constants.SUMMARY_VM_TYPE
|
||||
: (this._targetType === MigrationTargetType.SQLMI)
|
||||
? constants.SUMMARY_MI_TYPE
|
||||
: constants.SUMMARY_SQLDB_TYPE;
|
||||
|
||||
return this._view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: target,
|
||||
CSSStyles: { ...styles.PAGE_SUBTITLE_CSS }
|
||||
}).component();
|
||||
}
|
||||
|
||||
private createRecommendationComponent(): azdata.TextComponent {
|
||||
this._dbName = this._view.modelBuilder.text().withProps({
|
||||
CSSStyles: {
|
||||
...styles.LABEL_CSS,
|
||||
'margin-bottom': '8px',
|
||||
'font-weight': '700'
|
||||
}
|
||||
}).component();
|
||||
|
||||
return this._dbName;
|
||||
}
|
||||
|
||||
private createAssessmentResultsTitle(): azdata.TextComponent {
|
||||
this._recommendationTitle = this._view.modelBuilder.text().withProps({
|
||||
value: constants.WARNINGS,
|
||||
CSSStyles: {
|
||||
...styles.LABEL_CSS,
|
||||
'margin': '0 8px 4px 0',
|
||||
'width': '220px',
|
||||
}
|
||||
}).component();
|
||||
|
||||
return this._recommendationTitle;
|
||||
}
|
||||
|
||||
private createAssessmentDetailsTitle(): azdata.TextComponent {
|
||||
this._recommendation = this._view.modelBuilder.text().withProps({
|
||||
value: constants.WARNINGS_DETAILS,
|
||||
CSSStyles: {
|
||||
...styles.LABEL_CSS,
|
||||
'margin': '0 0 4px 24px',
|
||||
'width': '200px',
|
||||
}
|
||||
}).component();
|
||||
|
||||
return this._recommendation;
|
||||
}
|
||||
|
||||
private createImpactedObjectsTable(): azdata.FlexContainer {
|
||||
|
||||
this._assessmentResultsList = this._view.modelBuilder.listView().withProps({
|
||||
width: '200px',
|
||||
options: []
|
||||
}).component();
|
||||
|
||||
this._disposables.push(this._assessmentResultsList.onDidClick(async (e: azdata.ListViewClickEvent) => {
|
||||
const selectedIssue = this._activeIssues[parseInt(this._assessmentResultsList.selectedOptionId!)];
|
||||
await this.refreshAssessmentDetails(selectedIssue);
|
||||
}));
|
||||
|
||||
const container = this._view.modelBuilder.flexContainer()
|
||||
.withItems([this._assessmentResultsList])
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
height: '100%'
|
||||
})
|
||||
.withProps({ CSSStyles: { 'border-right': 'solid 1px' } })
|
||||
.component();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
public selectedDbs(): string[] {
|
||||
let result: string[] = [];
|
||||
this._databaseTable.dataValues?.forEach((arr, index) => {
|
||||
if (arr[0].value === true) {
|
||||
result.push(this._dbNames[index]);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public async refreshResults(): Promise<void> {
|
||||
if (this._targetType === MigrationTargetType.SQLMI ||
|
||||
this._targetType === MigrationTargetType.SQLDB) {
|
||||
if (this._activeIssues?.length === 0) {
|
||||
/// show no issues here
|
||||
await this._assessmentsTable.updateCssStyles({ 'display': 'none', 'border-right': 'none' });
|
||||
await this._assessmentContainer.updateCssStyles({ 'display': 'none' });
|
||||
await this._noIssuesContainer.updateCssStyles({ 'display': 'flex' });
|
||||
} else {
|
||||
await this._assessmentContainer.updateCssStyles({ 'display': 'flex' });
|
||||
await this._assessmentsTable.updateCssStyles({ 'display': 'flex', 'border-right': 'solid 1px' });
|
||||
await this._noIssuesContainer.updateCssStyles({ 'display': 'none' });
|
||||
}
|
||||
} else {
|
||||
await this._assessmentsTable.updateCssStyles({ 'display': 'none', 'border-right': 'none' });
|
||||
await this._assessmentContainer.updateCssStyles({ 'display': 'none' });
|
||||
await this._noIssuesContainer.updateCssStyles({ 'display': 'flex' });
|
||||
|
||||
this._recommendationTitle.value = constants.ASSESSMENT_RESULTS;
|
||||
this._recommendation.value = '';
|
||||
}
|
||||
let assessmentResults: azdata.ListViewOption[] = this._activeIssues
|
||||
.sort((e1, e2) => {
|
||||
if (e1.databaseRestoreFails) { return -1; }
|
||||
if (e2.databaseRestoreFails) { return 1; }
|
||||
return e1.checkId.localeCompare(e2.checkId);
|
||||
}).filter((v) => {
|
||||
return v.appliesToMigrationTargetPlatform === this._targetType;
|
||||
}).map((v, index) => {
|
||||
return {
|
||||
id: index.toString(),
|
||||
label: v.checkId,
|
||||
icon: v.databaseRestoreFails ? IconPathHelper.error : undefined,
|
||||
ariaLabel: v.databaseRestoreFails ? constants.BLOCKING_ISSUE_ARIA_LABEL(v.checkId) : v.checkId,
|
||||
};
|
||||
});
|
||||
|
||||
this._assessmentResultsList.options = assessmentResults;
|
||||
if (this._assessmentResultsList.options.length) {
|
||||
this._assessmentResultsList.selectedOptionId = '0';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public async refreshAssessmentDetails(selectedIssue?: SqlMigrationAssessmentResultItem): Promise<void> {
|
||||
await this._assessmentTitle.updateProperty('value', selectedIssue?.checkId || '');
|
||||
await this._descriptionText.updateProperty('value', selectedIssue?.description || '');
|
||||
await this._recommendationText.updateProperty('value', selectedIssue?.message || constants.NA);
|
||||
|
||||
if (selectedIssue?.helpLink) {
|
||||
await this._moreInfoTitle.updateProperty('display', 'flex');
|
||||
await this._moreInfoText.updateProperties({
|
||||
'display': 'flex',
|
||||
'url': selectedIssue?.helpLink || '',
|
||||
'label': selectedIssue?.displayName || '',
|
||||
'ariaLabel': selectedIssue?.displayName || '',
|
||||
'showLinkIcon': true
|
||||
});
|
||||
} else {
|
||||
await this._moreInfoTitle.updateProperty('display', 'none');
|
||||
await this._moreInfoText.updateProperties({
|
||||
'display': 'none',
|
||||
'url': '',
|
||||
'label': '',
|
||||
'ariaLabel': '',
|
||||
'showLinkIcon': false
|
||||
});
|
||||
}
|
||||
|
||||
this._impactedObjects = selectedIssue?.impactedObjects || [];
|
||||
await this._impactedObjectsTable.setDataValues(
|
||||
this._impactedObjects.map(
|
||||
(object) => [{ value: object.objectType }, { value: object.name }]));
|
||||
|
||||
this._impactedObjectsTable.selectedRow = this._impactedObjects?.length > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
public refreshImpactedObject(impactedObject?: SqlMigrationImpactedObjectInfo): void {
|
||||
this._objectDetailsType.value = constants.IMPACT_OBJECT_TYPE(impactedObject?.objectType);
|
||||
this._objectDetailsName.value = constants.IMPACT_OBJECT_NAME(impactedObject?.name);
|
||||
this._objectDetailsSample.value = impactedObject?.impactDetail || '';
|
||||
}
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
let instanceTableValues: azdata.DeclarativeTableCellValue[][] = [];
|
||||
this._databaseTableValues = [];
|
||||
this._dbNames = this._model._databasesForAssessment;
|
||||
this._serverName = (await getSourceConnectionProfile()).serverName;
|
||||
|
||||
// pre-select the entire list
|
||||
const selectedDbs = this._dbNames.filter(db => this._model._databasesForAssessment.includes(db));
|
||||
|
||||
if (this._targetType === MigrationTargetType.SQLVM || !this._model._assessmentResults) {
|
||||
instanceTableValues = [[
|
||||
{
|
||||
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
|
||||
style: styleLeft
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
style: styleRight
|
||||
}
|
||||
]];
|
||||
this._dbNames.forEach((db) => {
|
||||
this._databaseTableValues.push([
|
||||
{
|
||||
value: selectedDbs.includes(db),
|
||||
style: styleLeft
|
||||
},
|
||||
{
|
||||
value: this.createIconTextCell(IconPathHelper.sqlDatabaseLogo, db),
|
||||
style: styleLeft
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
style: styleRight
|
||||
}
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
instanceTableValues = [[
|
||||
{
|
||||
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
|
||||
style: styleLeft
|
||||
},
|
||||
{
|
||||
value: this._model._assessmentResults?.issues?.filter(issue => issue.appliesToMigrationTargetPlatform === this._targetType).length,
|
||||
style: styleRight
|
||||
}
|
||||
]];
|
||||
this._model._assessmentResults?.databaseAssessments
|
||||
.sort((db1, db2) => db2.issues?.length - db1.issues?.length);
|
||||
|
||||
// Reset the dbName list so that it is in sync with the table
|
||||
this._dbNames = this._model._assessmentResults?.databaseAssessments.map(da => da.name);
|
||||
this._model._assessmentResults?.databaseAssessments.forEach((db) => {
|
||||
let selectable = true;
|
||||
if (db.issues.find(issue => issue.databaseRestoreFails && issue.appliesToMigrationTargetPlatform === this._targetType)) {
|
||||
selectable = false;
|
||||
}
|
||||
this._databaseTableValues.push([
|
||||
{
|
||||
value: selectedDbs.includes(db.name) && selectable,
|
||||
style: styleLeft,
|
||||
enabled: selectable
|
||||
},
|
||||
{
|
||||
value: this.createIconTextCell((selectable) ? IconPathHelper.sqlDatabaseLogo : IconPathHelper.sqlDatabaseWarningLogo, db.name),
|
||||
style: styleLeft
|
||||
},
|
||||
{
|
||||
value: db.issues.filter(v => v.appliesToMigrationTargetPlatform === this._targetType)?.length,
|
||||
style: styleRight
|
||||
}
|
||||
]);
|
||||
});
|
||||
}
|
||||
await this._instanceTable.setDataValues(instanceTableValues);
|
||||
|
||||
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._databasesForAssessment?.length)
|
||||
});
|
||||
}
|
||||
|
||||
private createIconTextCell(icon: IconPath, text: string): string {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user