Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SQL Migration][New Migration Experience] Assessment Results Page Data Integration and Condition Handling #24378

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions extensions/sql-migration/images/sqlDatabaseNotReady.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions extensions/sql-migration/src/constants/iconPathHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class IconPathHelper {
public static sqlServerLogo: IconPath;
public static sqlDatabaseLogo: IconPath;
public static sqlDatabaseWarningLogo: IconPath;
public static sqlDatabaseNotReadyLogo: IconPath;
public static cancel: IconPath;
public static warning: IconPath;
public static info: IconPath;
Expand Down Expand Up @@ -119,6 +120,10 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/sqlDatabaseWarning.svg'),
dark: context.asAbsolutePath('images/sqlDatabaseWarning.svg')
};
IconPathHelper.sqlDatabaseNotReadyLogo = {
light: context.asAbsolutePath('images/sqlDatabaseNotReady.svg'),
dark: context.asAbsolutePath('images/sqlDatabaseNotReady.svg')
};
IconPathHelper.cancel = {
light: context.asAbsolutePath('images/cancel.svg'),
dark: context.asAbsolutePath('images/cancel.svg')
Expand Down
10 changes: 10 additions & 0 deletions extensions/sql-migration/src/constants/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ export const ASSESSED_DBS_LABEL = localize('sql.migration.assessed.dbs.label', "
export const NOT_READY = localize('sql.migration.not.ready', "Not ready");
export const READY = localize('sql.migration.ready', "Ready");
export const READY_WARN = localize('sql.migration.ready.warn', "Ready with warnings");
export const DATABASE_ISSUES_SUMMARY = localize('sql.migration.database.issues.summary', "Database assessment issues summary");
export const TOTAL_ISSUES_LABEL = localize('sql.migration.total.issues.label', "Total issues found");
export const SEVERITY_ISSUES_LABEL = localize('sql.migration.severity.issues.label', "Issues by severity");
export function DB_READINESS_SECTION_TITLE(dbName: string) {
return localize('sql.migration.db.readiness.section.title', "Database {0} migration readiness", dbName);
}
export function NON_READINESS_DESCRIPTION(issueCount: number) {
return localize('sql.migration.non.readiness.description', "The database is not ready to migrate due to {0} blocking issue.", issueCount);
}
export const READINESS_DESCRIPTION = localize('sql.migration.readiness.description', "The database is ready to migrate.");

// Assessment results and recommendations
export const ASSESSMENT_RESULTS_AND_RECOMMENDATIONS_PAGE_TITLE = localize('sql.migration.assessment.results.and.recommendations.title', "Assessment results and recommendations");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class AssessmentDetailsPage extends MigrationWizardPage {
azdata.window.createWizardPage(constants.ASSESSMENT_RESULTS_PAGE_TITLE),
migrationStateModel);
this._header = new AssessmentDetailsHeader(migrationStateModel);
this._body = new AssessmentDetailsBody(migrationStateModel);
this._body = new AssessmentDetailsBody(migrationStateModel, migrationStateModel._targetType);
}

// function to register Assessment details page content.
Expand Down Expand Up @@ -62,7 +62,7 @@ export class AssessmentDetailsPage extends MigrationWizardPage {

public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
await this._header.populateAssessmentDetailsHeader();
await this._body._treeComponent.initialize();
await this._body.populateAssessmentBody();
}

public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,36 @@
*--------------------------------------------------------------------------------------------*/

import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as constants from '../../constants/strings';
import * as styles from '../../constants/styles';
import { MigrationStateModel } from '../../models/stateMachine';
import { MigrationTargetType } from '../../api/utils';
import { TreeComponent } from './treeComponent';
import { InstanceSummary } from './instanceSummary';
import { IconPathHelper } from '../../constants/iconPathHelper';
import { DatabaseSummary } from './databaseSummary';
import { IssueSummary } from './issueSummary';
import { SqlMigrationAssessmentResultItem } from '../../service/contracts';

// Class that defines ui for body section of assessment result page
export class AssessmentDetailsBody {
private _view!: azdata.ModelView;
private _model!: MigrationStateModel;
public _treeComponent!: TreeComponent;
private _targetType!: MigrationTargetType;
private _instanceSummary = new InstanceSummary();
public _instanceSummary = new InstanceSummary();
private _databaseSummary = new DatabaseSummary();
private _issueSummary = new IssueSummary();
public _warningsOrIssuesListSection!: azdata.ListViewComponent;
private _findingsSummaryList!: azdata.ListViewComponent;
private _disposables: vscode.Disposable[] = [];
private _activeIssues!: SqlMigrationAssessmentResultItem[];

constructor(migrationStateModel: MigrationStateModel) {
constructor(migrationStateModel: MigrationStateModel,
targetType: MigrationTargetType) {
this._model = migrationStateModel;
this._targetType = targetType;
this._treeComponent = new TreeComponent(this._model, this._model._targetType)
}

Expand All @@ -47,16 +60,22 @@ export class AssessmentDetailsBody {
// returns middle section of body where list of issues and warnings are displayed.
const assessmentFindingsComponent = this.createAssessmentFindingComponent();

// returns the right section of body which defines summary of selected instance.
const instanceSummary = this._instanceSummary.createInstanceSummaryContainer(view);
// returns the right section of body which defines the result of assessment.
const resultComponent = this.createResultComponent();

bodyContainer.addItem(treeComponent, { flex: "none" });
bodyContainer.addItem(assessmentFindingsComponent, { flex: "none" });
bodyContainer.addItem(instanceSummary, { flex: "none" });
bodyContainer.addItem(resultComponent, { flex: "none" });

return bodyContainer;
}

// function to populate data for body section
public async populateAssessmentBody(): Promise<void> {
await this._treeComponent.initialize();
this._instanceSummary.populateInstanceSummaryContainer(this._model, this._model._targetType);
}

// function to create middle section of body that displays list of warnings/ issues.
public createAssessmentFindingComponent(): azdata.FlexContainer {
const assessmentFindingsComponent = this._view.modelBuilder.flexContainer().withLayout({
Expand All @@ -68,22 +87,185 @@ export class AssessmentDetailsBody {
}
}).component();

const findingsSection = this._view.modelBuilder.listView().withProps({
this._findingsSummaryList = this._view.modelBuilder.listView().withProps({
title: {
text: constants.ASSESSMENT_FINDINGS_LABEL,
style: { "border": "solid 1px" }
},
options: [{ label: constants.SUMMARY_TITLE, id: "summary" }]
}).component();

assessmentFindingsComponent.addItem(findingsSection);
assessmentFindingsComponent.addItem(this._findingsSummaryList);

const warningsOrIssuesListSection = this._view.modelBuilder.listView().withProps({
this._warningsOrIssuesListSection = this._view.modelBuilder.listView().withProps({
title: { text: constants.WARNINGS },
options: [] // TODO: fill the list of options.
options: []
}).component();

assessmentFindingsComponent.addItem(warningsOrIssuesListSection);
assessmentFindingsComponent.addItem(this._warningsOrIssuesListSection);
return assessmentFindingsComponent;
}

private createResultComponent(): azdata.FlexContainer {
const resultContainer = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).withProps({
CSSStyles: {
'border-left': 'solid 1px'
}
}).component();

const heading = this._view.modelBuilder.text().withProps({
value: constants.DETAILS_TITLE,
CSSStyles: {
...styles.LABEL_CSS,
"padding": "5px",
"border-bottom": "solid 1px"
}
}).component();

const subHeading = this._view.modelBuilder.text().withProps({
value: constants.ASSESSMENT_SUMMARY_TITLE,
CSSStyles: {
...styles.LABEL_CSS,
"padding": "5px",
"padding-left": "10px",
"padding-right": "250px",
"border-bottom": "solid 1px"
}
}).component();

const bottomContainer = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).component();

// returns the right section of body which defines summary of selected instance.
const instanceSummary = this._instanceSummary.createInstanceSummaryContainer(this._view);

// returns the right section of body which defines summary of selected database.
const databaseSummary = this._databaseSummary.createDatabaseSummary(this._view);
databaseSummary.display = 'none';

// returns the right section of body which defines summary of selected database.
const issueSummary = this._issueSummary.createIssueSummary(this._view);
issueSummary.display = 'none';

bottomContainer.addItems([instanceSummary, databaseSummary, issueSummary]);

resultContainer.addItems([heading, subHeading, bottomContainer]);

let _isInstanceSummarySelected = true;

this._disposables.push(this._treeComponent.instanceTable.onRowSelected(async (e) => {
_isInstanceSummarySelected = true;
this._targetType = this._model?._targetType;
this._activeIssues = this._model._assessmentResults?.issues.filter(issue => issue.appliesToMigrationTargetPlatform === this._targetType);
await instanceSummary.updateCssStyles({
'display': 'block'
});
await databaseSummary.updateCssStyles({
'display': 'none'
});
await issueSummary.updateCssStyles({
'display': 'none'
});
await subHeading.updateProperty('value', constants.ASSESSMENT_SUMMARY_TITLE);
if (this._targetType === MigrationTargetType.SQLMI ||
this._targetType === MigrationTargetType.SQLDB) {
await this.refreshResults();
}
this._instanceSummary.populateInstanceSummaryContainer(this._model, this._targetType);
}));

this._disposables.push(this._treeComponent.databaseTable.onRowSelected(async (e) => {
_isInstanceSummarySelected = false;
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 = [];
}
await instanceSummary.updateCssStyles({
'display': 'none'
});
await issueSummary.updateCssStyles({
'display': 'none'
});
await databaseSummary.updateCssStyles({
'display': 'block'
});
await subHeading.updateProperty('value', constants.ASSESSMENT_SUMMARY_TITLE);
await this._databaseSummary.populateDatabaseSummary(this._activeIssues, this._model._assessmentResults?.databaseAssessments[e.row]?.name);
if (this._targetType === MigrationTargetType.SQLMI ||
this._targetType === MigrationTargetType.SQLDB) {
await this.refreshResults();
}
if (this._findingsSummaryList.options.length) {
this._findingsSummaryList.selectedOptionId = '0';
}
}));

this._disposables.push(this._findingsSummaryList.onDidClick(async (e: azdata.ListViewClickEvent) => {
if (_isInstanceSummarySelected) {
await instanceSummary.updateCssStyles({
'display': 'block'
});
await databaseSummary.updateCssStyles({
'display': 'none'
});
}
else {
await instanceSummary.updateCssStyles({
'display': 'none'
});
await databaseSummary.updateCssStyles({
'display': 'block'
});
}
await issueSummary.updateCssStyles({
'display': 'none'
});
await subHeading.updateProperty('value', constants.ASSESSMENT_SUMMARY_TITLE);
}));

this._disposables.push(this._warningsOrIssuesListSection.onDidClick(async (e: azdata.ListViewClickEvent) => {
const selectedIssue = this._activeIssues[parseInt(this._warningsOrIssuesListSection.selectedOptionId!)];
await instanceSummary.updateCssStyles({
'display': 'none'
});
await databaseSummary.updateCssStyles({
'display': 'none'
});
await issueSummary.updateCssStyles({
'display': 'block'
});
await subHeading.updateProperty('value', selectedIssue?.checkId || '');
await this._issueSummary.refreshAssessmentDetails(selectedIssue);
}));

return resultContainer;
}

public async refreshResults(): Promise<void> {
if (this._targetType === MigrationTargetType.SQLMI ||
this._targetType === MigrationTargetType.SQLDB) {
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._warningsOrIssuesListSection.options = assessmentResults;
}
}
}
Loading