diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts index 7343bfdb61..8bf604599d 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts @@ -19,6 +19,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { Codicon } from 'vs/base/common/codicons'; +import { deepClone } from 'vs/base/common/objects'; export enum ExecutionPlanCompareOrientation { Horizontal = 'horizontal', @@ -173,7 +174,8 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti secondaryProps = this._model.secondaryElement.properties; } - const tableRows = this.convertPropertiesToTableRows(primaryProps, secondaryProps); + let tableRows = this.convertPropertiesToTableRows(primaryProps, secondaryProps); + tableRows = this.sortPropertiesByDisplayValueEquivalency(tableRows); this.setSummaryElement(this.getExpensivePropertySummary(tableRows)); this.populateTable(columns, tableRows); } @@ -189,14 +191,14 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti * @param tableRows The table rows that will appear in the properties table. * @returns The string array containing the segments of the summary. */ - private getExpensivePropertySummary(tableRows: { [key: string]: string }[]): string[] { + private getExpensivePropertySummary(tableRows: TableRow[]): string[] { let summary: string[] = []; tableRows.forEach(row => { - const rowName = row.name['text']; + const rowName = (row.name).text; if (row.primary && row.secondary) { - const primaryText = row.primary['text'].split(' '); - const secondaryTitle = row.secondary['title'].split(' '); + const primaryText = row.primary.text.split(' '); + const secondaryTitle = row.secondary.title.split(' '); if (primaryText.length === secondaryTitle.length && primaryText.length <= 2 && secondaryTitle.length <= 2) { const MAX_PROPERTY_SUMMARY_LENGTH = 3; @@ -310,46 +312,71 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti * * Name Value (Top plan) Value (Bottom Plan) * ------------------------------------------------------------------------- - * Compile Time 38 37 - * CompileCpu 38 37 - * CompileMemory 5816 6424 - * Estimated Number of Rows 1000 1000 - * Optimization Level FULL FULL - * RetrievedFromCache false false + * Compile Time 38 37 + * CompileCpu 38 37 + * CompileMemory 5816 6424 + * Estimated Number of Rows 1000 1000 + * Optimization Level FULL FULL + * RetrievedFromCache false false * - * @param props Map of properties that will be organized. - * @returns A new map with different values appearing at the top and similar values appearing at the bottom. + * @param rows An array of TableRows that contains all the properties that will be organized. + * @returns A new array of TableRows with unequal values appearing at the top and equal values appearing at the bottom. */ - public sortPropertiesByDisplayValueEquivalency(props: Map, sortProperties: (props: Map) => Map): Map { - let unequalProperties: Map = new Map(); - let equalProperties: Map = new Map(); + public sortPropertiesByDisplayValueEquivalency(rows: TableRow[]): TableRow[] { + const [unequalPropertyRows, equalPropertyRows] = this.splitEqualFromUnequalProperties(rows); - [...props.entries()].forEach(prop => { - const [rowKey, rowEntry] = prop; - const primaryProp = rowEntry.primaryProp; - const secondaryProp = rowEntry.secondaryProp; + const organizedPropertyRows: TableRow[] = [...unequalPropertyRows]; - if (primaryProp?.displayValue.localeCompare(secondaryProp?.displayValue) === 0) { - equalProperties.set(rowKey, rowEntry); + if (equalPropertyRows.length > 0) { + const equivalentPropertiesRow: TableRow = new Object() as TableRow; + equivalentPropertiesRow.name = equivalentPropertiesRowHeader; + equivalentPropertiesRow.expanded = false; + equivalentPropertiesRow.treeGridChildren = equalPropertyRows; + + organizedPropertyRows.push(equivalentPropertiesRow); + } + + return organizedPropertyRows; + } + + private splitEqualFromUnequalProperties(rows: TableRow[]): [TableRow[], TableRow[]] { + const unequalRows: TableRow[] = []; + const equalRows: TableRow[] = []; + + for (let row of rows) { + const treeGridChildren = row.treeGridChildren; + + if (treeGridChildren?.length > 0) { + let [unequalSubRows, equalSubRows] = this.splitEqualFromUnequalProperties(treeGridChildren); + + if (unequalSubRows.length > 0) { + let currentRow = deepClone(row); + currentRow.treeGridChildren = unequalSubRows; + + unequalRows.push(currentRow); + } + + if (equalSubRows.length > 0) { + let currentRow = deepClone(row); + currentRow.treeGridChildren = equalSubRows; + + equalRows.push(currentRow); + } } else { - unequalProperties.set(rowKey, rowEntry); + const primary = row.primary; + const secondary = row.secondary; + + if (primary && secondary && primary.text === secondary.title) { + equalRows.push(row); + } + else { + unequalRows.push(row); + } } - }); + } - unequalProperties = sortProperties(unequalProperties); - equalProperties = sortProperties(equalProperties); - - let map: Map = new Map(); - unequalProperties.forEach((v, k) => { - map.set(k, v); - }); - - equalProperties.forEach((v, k) => { - map.set(k, v); - }); - - return map; + return [unequalRows, equalRows]; } public sortPropertiesReverseAlphabetically(props: Map): Map { @@ -366,8 +393,8 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti })); } - private convertPropertiesToTableRows(primaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[], secondaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[]): { [key: string]: string }[] { - const rows: { [key: string]: string }[] = []; + private convertPropertiesToTableRows(primaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[], secondaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[]): TableRow[] { + const rows: TableRow[] = []; let propertiesMap: Map = new Map(); if (primaryNode) { @@ -398,28 +425,27 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti switch (this.sortType) { case PropertiesSortType.DisplayOrder: - propertiesMap = this.sortPropertiesByDisplayValueEquivalency(propertiesMap, this.sortPropertiesByImportance); + propertiesMap = this.sortPropertiesByImportance(propertiesMap); break; case PropertiesSortType.Alphabetical: - propertiesMap = this.sortPropertiesByDisplayValueEquivalency(propertiesMap, this.sortPropertiesAlphabetically); + propertiesMap = this.sortPropertiesAlphabetically(propertiesMap); break; case PropertiesSortType.ReverseAlphabetical: - propertiesMap = this.sortPropertiesByDisplayValueEquivalency(propertiesMap, this.sortPropertiesReverseAlphabetically); + propertiesMap = this.sortPropertiesReverseAlphabetically(propertiesMap); break; } propertiesMap.forEach((v, k) => { - let row = {}; - row['name'] = { + let row: TableRow = new Object() as TableRow; + row.name = { text: k }; const primaryProp = v.primaryProp; const secondaryProp = v.secondaryProp; - let diffIconClass = ''; if (primaryProp && secondaryProp) { - row['displayOrder'] = v.primaryProp.displayOrder; + row.displayOrder = v.primaryProp.displayOrder; let diffIcon = new Object() as DiffIcon; if (v.primaryProp.displayValue !== v.secondaryProp.displayValue) { @@ -445,80 +471,62 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti } } - row['primary'] = { + row.primary = { text: removeLineBreaks(v.primaryProp.displayValue, ' ') }; - row['icon'] = { - iconCssClass: diffIcon.iconClass, - title: diffIcon.title + row.icon = { + iconCssClass: diffIcon.iconClass ?? '', + title: diffIcon.title ?? '' }; - row['secondary'] = { - title: removeLineBreaks(v.secondaryProp.displayValue, ' ') + row.secondary = { + title: removeLineBreaks(v.secondaryProp.displayValue, ' '), }; if ((primaryProp && !isString(primaryProp.value)) || (secondaryProp && !isString(secondaryProp.value))) { - row['name'].iconCssClass += ` parent-row-styling`; - row['primary'].iconCssClass += ` parent-row-styling`; - row['icon'].iconCssClass += 'parent-row-styling'; - row['secondary'].iconCssClass += ` parent-row-styling`; + const parentRowStyling = ' parent-row-styling'; + + row.name.iconCssClass = !row.name.iconCssClass ? parentRowStyling : row.name.iconCssClass + parentRowStyling; + row.primary.iconCssClass = !row.primary.iconCssClass ? parentRowStyling : row.primary.iconCssClass + parentRowStyling; + row.icon.iconCssClass = !row.icon.iconCssClass ? parentRowStyling : row.icon.iconCssClass + parentRowStyling; + row.secondary.iconCssClass = !row.secondary.iconCssClass ? parentRowStyling : row.secondary.iconCssClass + parentRowStyling; } rows.push(row); const topPropValue = isString(primaryProp.value) ? undefined : primaryProp.value; const bottomPropValue = isString(secondaryProp.value) ? undefined : secondaryProp.value; - row['treeGridChildren'] = this.convertPropertiesToTableRows(topPropValue, bottomPropValue); + row.treeGridChildren = this.convertPropertiesToTableRows(topPropValue, bottomPropValue); } else if (primaryProp && !secondaryProp) { - row['displayOrder'] = v.primaryProp.displayOrder; - row['primary'] = { + row.displayOrder = v.primaryProp.displayOrder; + row.primary = { text: v.primaryProp.displayValue }; rows.push(row); if (!isString(primaryProp.value)) { - row['name'].iconCssClass += ` parent-row-styling`; - row['primary'].iconCssClass += ` parent-row-styling`; - row['treeGridChildren'] = this.convertPropertiesToTableRows(primaryProp.value, undefined); + row.name.iconCssClass += ` parent-row-styling`; + row.primary.iconCssClass += ` parent-row-styling`; + row.treeGridChildren = this.convertPropertiesToTableRows(primaryProp.value, undefined); } } else if (!primaryProp && secondaryProp) { - row['displayOrder'] = v.secondaryProp.displayOrder; - row['secondary'] = { + row.displayOrder = v.secondaryProp.displayOrder; + row.secondary = { title: v.secondaryProp.displayValue, - iconCssClass: diffIconClass + iconCssClass: '' }; rows.push(row); if (!isString(secondaryProp.value)) { - row['name'].iconCssClass += ` parent-row-styling`; - row['secondary'].iconCssClass += ` parent-row-styling`; - row['treeGridChildren'] = this.convertPropertiesToTableRows(undefined, secondaryProp.value); + row.name.iconCssClass += ` parent-row-styling`; + row.secondary.iconCssClass += ` parent-row-styling`; + row.treeGridChildren = this.convertPropertiesToTableRows(undefined, secondaryProp.value); } } }); - let formattedRows: { [key: string]: string }[] = []; - let equalRows: { [key: string]: string }[] = []; - for (const [_, row] of Object.entries(rows)) { - if (row.primary && row.secondary && row.primary['text'] === row.secondary['title']) { - equalRows.push(row); - } - else { - formattedRows.push(row); - } - } - - if (equalRows.length > 0) { - let equalRow = {}; - equalRow['name'] = equivalentPropertiesRowHeader; - equalRow['expanded'] = false; - equalRow['treeGridChildren'] = equalRows; - - formattedRows.push(equalRow); - } - - return formattedRows; + return rows; } set orientation(value: ExecutionPlanCompareOrientation) { @@ -564,3 +572,19 @@ interface DiffIcon { iconClass: string; title: string; } + +interface TableRow extends Slick.SlickData { + displayOrder: number; + icon: RowContent; + name: RowContent | string; + primary: RowContent; + secondary: RowContent; + expanded: boolean; + treeGridChildren: TableRow[]; +} + +interface RowContent { + iconCssClass?: string; + text?: string; + title?: string; +}