mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Reorganized Execution Plan Comparison Properties View (#20673)
* Adds summary view to comparison properties view * Equal rows are collapsed at the bottom of the properties view window * Variable rename * Variable rename * Adds row header for equal properties. * Adds middle column with diff icon to comparison properties table * Resolves hygiene issue * Code review changes * Fixes undefined error bug * Adds styles to less than and greater than codicon's * Changes equivalent properties collapsed header * Removes unnecessary check * Integrates alphabetical and importance sorting to equivalency sorting
This commit is contained in:
@@ -111,6 +111,17 @@ export function textFormatter(row: number | undefined, cell: any | undefined, va
|
|||||||
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
|
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
|
||||||
titleValue = valueToDisplay;
|
titleValue = valueToDisplay;
|
||||||
}
|
}
|
||||||
|
else if (value && value.title) {
|
||||||
|
if (value.title) {
|
||||||
|
valueToDisplay = value.title;
|
||||||
|
|
||||||
|
if (value.style) {
|
||||||
|
cellStyle = value.style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
|
||||||
|
titleValue = valueToDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
return `<span title="${titleValue}" style="${cellStyle}" class="${cellClasses}">${valueToDisplay}</span>`;
|
return `<span title="${titleValue}" style="${cellStyle}" class="${cellClasses}">${valueToDisplay}</span>`;
|
||||||
}
|
}
|
||||||
@@ -118,7 +129,7 @@ export function textFormatter(row: number | undefined, cell: any | undefined, va
|
|||||||
|
|
||||||
export function iconCssFormatter(row: number | undefined, cell: any | undefined, value: any, columnDef: any | undefined, dataContext: any | undefined): string {
|
export function iconCssFormatter(row: number | undefined, cell: any | undefined, value: any, columnDef: any | undefined, dataContext: any | undefined): string {
|
||||||
if (isCssIconCellValue(value)) {
|
if (isCssIconCellValue(value)) {
|
||||||
return `<div role="image" title="${escape(value.title)}" aria-label="${escape(value.title)}" class="grid-cell-value-container icon codicon slick-icon-cell-content ${value.iconCssClass}"></div>`;
|
return `<div role="image" title="${escape(value.title ?? '')}" aria-label="${escape(value.title ?? '')}" class="grid-cell-value-container icon codicon slick-icon-cell-content ${value.iconCssClass}"></div>`;
|
||||||
}
|
}
|
||||||
return textFormatter(row, cell, value, columnDef, dataContext);
|
return textFormatter(row, cell, value, columnDef, dataContext);
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/sql/base/common/numbers.ts
Normal file
8
src/sql/base/common/numbers.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
export function isNumber(text: string): boolean {
|
||||||
|
return !isNaN(parseInt(text)) && !isNaN(parseFloat(text));
|
||||||
|
}
|
||||||
@@ -5,18 +5,20 @@
|
|||||||
|
|
||||||
import { ExecutionPlanPropertiesViewBase, PropertiesSortType } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase';
|
import { ExecutionPlanPropertiesViewBase, PropertiesSortType } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { isNumber } from 'sql/base/common/numbers';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
|
import { iconCssFormatter, textFormatter } from 'sql/base/browser/ui/table/formatters';
|
||||||
import { isString } from 'vs/base/common/types';
|
import { isString } from 'vs/base/common/types';
|
||||||
import { removeLineBreaks } from 'sql/base/common/strings';
|
import { removeLineBreaks } from 'sql/base/common/strings';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { InternalExecutionPlanElement } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
import { InternalExecutionPlanElement } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
||||||
import { executionPlanComparisonPropertiesDifferent, executionPlanComparisonPropertiesUpArrow, executionPlanComparisonPropertiesDownArrow } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
import { executionPlanComparisonPropertiesDifferent } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||||
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
|
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { TextWithIconColumn } from 'sql/base/browser/ui/table/plugins/textWithIconColumn';
|
|
||||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||||
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
|
|
||||||
export enum ExecutionPlanCompareOrientation {
|
export enum ExecutionPlanCompareOrientation {
|
||||||
Horizontal = 'horizontal',
|
Horizontal = 'horizontal',
|
||||||
@@ -39,6 +41,26 @@ function getRightOperationLabel(target: string): string {
|
|||||||
return localize('nodePropertyViewRightOperation', 'Right operation: {0}', target);
|
return localize('nodePropertyViewRightOperation', 'Right operation: {0}', target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTopPlanIsGreaterThanBottomPlanSummaryTextTemplate(rowName: string): string {
|
||||||
|
return localize('nodePropertyViewTopPlanGreaterThanBottomPlan', '{0} is greater for the top plan than it is for the bottom plan.', rowName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBottomPlanIsGreaterThanTopPlanSummaryTextTemplate(rowName: string): string {
|
||||||
|
return localize('nodePropertyViewBottomPlanGreaterThanTopPlan', '{0} is greater for the bottom plan than it is for the top plan.', rowName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLeftPlanIsGreaterThanRightPlanSummaryTextTemplate(rowName: string): string {
|
||||||
|
return localize('nodePropertyViewLeftPlanGreaterThanRightPlan', '{0} is greater for the left plan than it is for the right plan.', rowName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRightPlanIsGreaterThanLeftPlanSummaryTextTemplate(rowName: string): string {
|
||||||
|
return localize('nodePropertyViewRightPlanGreaterThanLeftPlan', '{0} is greater for the right plan than it is for the left plan.', rowName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const notEqualTitle = localize('nodePropertyViewNameNotEqualTitle', 'Not equal to');
|
||||||
|
const lessThanTitle = localize('nodePropertyViewNameLessThanTitle', 'Less than');
|
||||||
|
const greaterThanTitle = localize('nodePropertyViewNameGreaterThanTitle', 'Greater than');
|
||||||
|
const equivalentPropertiesRowHeader = localize('nodePropertyViewNameEquivalentPropertiesRowHeader', 'Equivalent Properties');
|
||||||
const topTitleColumnHeader = localize('nodePropertyViewNameValueColumnTopHeader', "Value (Top Plan)");
|
const topTitleColumnHeader = localize('nodePropertyViewNameValueColumnTopHeader', "Value (Top Plan)");
|
||||||
const leftTitleColumnHeader = localize('nodePropertyViewNameValueColumnLeftHeader', "Value (Left Plan)");
|
const leftTitleColumnHeader = localize('nodePropertyViewNameValueColumnLeftHeader', "Value (Left Plan)");
|
||||||
const rightTitleColumnHeader = localize('nodePropertyViewNameValueColumnRightHeader', "Value (Right Plan)");
|
const rightTitleColumnHeader = localize('nodePropertyViewNameValueColumnRightHeader', "Value (Right Plan)");
|
||||||
@@ -46,6 +68,7 @@ const bottomTitleColumnHeader = localize('nodePropertyViewNameValueColumnBottomH
|
|||||||
|
|
||||||
export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanPropertiesViewBase {
|
export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanPropertiesViewBase {
|
||||||
private _model: ExecutionPlanComparisonPropertiesViewModel;
|
private _model: ExecutionPlanComparisonPropertiesViewModel;
|
||||||
|
private _summaryTextContainer: HTMLElement;
|
||||||
private _primaryContainer: HTMLElement;
|
private _primaryContainer: HTMLElement;
|
||||||
private _secondaryContainer: HTMLElement;
|
private _secondaryContainer: HTMLElement;
|
||||||
private _orientation: ExecutionPlanCompareOrientation = ExecutionPlanCompareOrientation.Horizontal;
|
private _orientation: ExecutionPlanCompareOrientation = ExecutionPlanCompareOrientation.Horizontal;
|
||||||
@@ -57,19 +80,34 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
@IThemeService themeService: IThemeService,
|
@IThemeService themeService: IThemeService,
|
||||||
@IInstantiationService instantiationService: IInstantiationService,
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
@IContextMenuService contextMenuService: IContextMenuService,
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
@IContextViewService contextViewService: IContextViewService
|
@IContextViewService contextViewService: IContextViewService,
|
||||||
|
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
|
||||||
) {
|
) {
|
||||||
super(parentContainer, themeService, instantiationService, contextMenuService, contextViewService);
|
super(parentContainer, themeService, instantiationService, contextMenuService, contextViewService);
|
||||||
this._model = <ExecutionPlanComparisonPropertiesViewModel>{};
|
this._model = <ExecutionPlanComparisonPropertiesViewModel>{};
|
||||||
this._parentContainer.style.display = 'none';
|
this._parentContainer.style.display = 'none';
|
||||||
const header = DOM.$('.compare-operation-name');
|
const header = DOM.$('.compare-operation-name');
|
||||||
|
|
||||||
|
this._summaryTextContainer = DOM.$('.compare-operation-summary-text');
|
||||||
|
this.setSummary(this._summaryTextContainer);
|
||||||
|
|
||||||
this._primaryContainer = DOM.$('.compare-operation-name-text');
|
this._primaryContainer = DOM.$('.compare-operation-name-text');
|
||||||
header.appendChild(this._primaryContainer);
|
header.appendChild(this._primaryContainer);
|
||||||
|
|
||||||
this._secondaryContainer = DOM.$('.compare-operation-name-text');
|
this._secondaryContainer = DOM.$('.compare-operation-name-text');
|
||||||
header.appendChild(this._secondaryContainer);
|
header.appendChild(this._secondaryContainer);
|
||||||
|
|
||||||
this.setHeader(header);
|
this.setHeader(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setSummaryElement(summary: string[]): void {
|
||||||
|
const EOL = this.textResourcePropertiesService.getEOL(undefined);
|
||||||
|
let summaryText = summary.join(EOL);
|
||||||
|
let summaryContainerText = localize('executionPlanSummaryForExpensiveOperators', "Summary: {0}{1}", EOL, summaryText);
|
||||||
|
this._summaryTextContainer.innerText = summaryContainerText;
|
||||||
|
this._summaryTextContainer.title = summaryContainerText;
|
||||||
|
}
|
||||||
|
|
||||||
public setPrimaryElement(e: InternalExecutionPlanElement): void {
|
public setPrimaryElement(e: InternalExecutionPlanElement): void {
|
||||||
this._model.primaryElement = e;
|
this._model.primaryElement = e;
|
||||||
if ((<azdata.executionPlan.ExecutionPlanNode>e).name) {
|
if ((<azdata.executionPlan.ExecutionPlanNode>e).name) {
|
||||||
@@ -134,7 +172,60 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
if (this._model.secondaryElement?.properties) {
|
if (this._model.secondaryElement?.properties) {
|
||||||
secondaryProps = this._model.secondaryElement.properties;
|
secondaryProps = this._model.secondaryElement.properties;
|
||||||
}
|
}
|
||||||
this.populateTable(columns, this.convertPropertiesToTableRows(primaryProps, secondaryProps));
|
|
||||||
|
const tableRows = this.convertPropertiesToTableRows(primaryProps, secondaryProps);
|
||||||
|
this.setSummaryElement(this.getExpensivePropertySummary(tableRows));
|
||||||
|
this.populateTable(columns, tableRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns an array of strings that that will make up the properties summary. The properties summary
|
||||||
|
* will appear above the properties table when execution plans are being compared.
|
||||||
|
* Each segment of that summary is in the following generic format:
|
||||||
|
*
|
||||||
|
* <row-name> is greater for the top plan than it is for the bottom plan.
|
||||||
|
* <row-name> is greater for the bottom plan than it is for the top plan.
|
||||||
|
*
|
||||||
|
* @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[] {
|
||||||
|
let summary: string[] = [];
|
||||||
|
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
const rowName = row.name['text'];
|
||||||
|
if (row.primary && row.secondary) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (let i = 0; i < primaryText.length && summary.length < MAX_PROPERTY_SUMMARY_LENGTH; ++i) {
|
||||||
|
if (isNumber(primaryText[i]) && isNumber(secondaryTitle[i])) {
|
||||||
|
const primaryValue = Number(primaryText);
|
||||||
|
const secondaryValue = Number(secondaryTitle);
|
||||||
|
|
||||||
|
let summaryText: string;
|
||||||
|
if (primaryValue > secondaryValue) {
|
||||||
|
summaryText = this._orientation === ExecutionPlanCompareOrientation.Horizontal
|
||||||
|
? getTopPlanIsGreaterThanBottomPlanSummaryTextTemplate(rowName)
|
||||||
|
: getLeftPlanIsGreaterThanRightPlanSummaryTextTemplate(rowName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
summaryText = this._orientation === ExecutionPlanCompareOrientation.Horizontal
|
||||||
|
? getBottomPlanIsGreaterThanTopPlanSummaryTextTemplate(rowName)
|
||||||
|
: getRightPlanIsGreaterThanLeftPlanSummaryTextTemplate(rowName);
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.push(summaryText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPropertyTableColumns() {
|
private getPropertyTableColumns() {
|
||||||
@@ -159,13 +250,23 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this._model.secondaryElement) {
|
if (this._model.secondaryElement) {
|
||||||
columns.push(new TextWithIconColumn({
|
columns.push({
|
||||||
|
id: 'comparison',
|
||||||
|
name: '',
|
||||||
|
field: 'icon',
|
||||||
|
width: 40,
|
||||||
|
headerCssClass: 'prop-table-header',
|
||||||
|
formatter: iconCssFormatter
|
||||||
|
});
|
||||||
|
|
||||||
|
columns.push({
|
||||||
id: 'value2',
|
id: 'value2',
|
||||||
name: getPropertyViewNameValueColumnBottomHeaderForOrientation(this._orientation),
|
name: getPropertyViewNameValueColumnBottomHeaderForOrientation(this._orientation),
|
||||||
field: 'secondary',
|
field: 'secondary',
|
||||||
width: 150,
|
width: 150,
|
||||||
headerCssClass: 'prop-table-header',
|
headerCssClass: 'prop-table-header',
|
||||||
}).definition);
|
formatter: textFormatter
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
@@ -188,16 +289,69 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
return new Map([...props.entries()].sort((a, b) => {
|
return new Map([...props.entries()].sort((a, b) => {
|
||||||
if (!a[1]?.displayOrder && !b[1]?.displayOrder) {
|
if (!a[1]?.displayOrder && !b[1]?.displayOrder) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (!a[1]?.displayOrder) {
|
}
|
||||||
|
else if (!a[1]?.displayOrder) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (!b[1]?.displayOrder) {
|
}
|
||||||
|
else if (!b[1]?.displayOrder) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return a[1].displayOrder - b[1].displayOrder;
|
return a[1].displayOrder - b[1].displayOrder;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will sort properties by having those with different values appear at the top,
|
||||||
|
* and similar values appearing at the bottom of the table.
|
||||||
|
*
|
||||||
|
* An example of this sort of sorting looks like this:
|
||||||
|
*
|
||||||
|
* Name Value (Top plan) Value (Bottom Plan)
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
* Compile Time 38 37 <diff>
|
||||||
|
* CompileCpu 38 37 <diff>
|
||||||
|
* CompileMemory 5816 6424 <diff>
|
||||||
|
* Estimated Number of Rows 1000 1000 <same>
|
||||||
|
* Optimization Level FULL FULL <same>
|
||||||
|
* RetrievedFromCache false false <same>
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
public sortPropertiesByDisplayValueEquivalency(props: Map<string, TablePropertiesMapEntry>, sortProperties: (props: Map<string, TablePropertiesMapEntry>) => Map<string, TablePropertiesMapEntry>): Map<string, TablePropertiesMapEntry> {
|
||||||
|
let unequalProperties: Map<string, TablePropertiesMapEntry> = new Map();
|
||||||
|
let equalProperties: Map<string, TablePropertiesMapEntry> = new Map();
|
||||||
|
|
||||||
|
[...props.entries()].forEach(prop => {
|
||||||
|
const [rowKey, rowEntry] = prop;
|
||||||
|
const primaryProp = rowEntry.primaryProp;
|
||||||
|
const secondaryProp = rowEntry.secondaryProp;
|
||||||
|
|
||||||
|
if (primaryProp?.displayValue.localeCompare(secondaryProp?.displayValue) === 0) {
|
||||||
|
equalProperties.set(rowKey, rowEntry);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unequalProperties.set(rowKey, rowEntry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
unequalProperties = sortProperties(unequalProperties);
|
||||||
|
equalProperties = sortProperties(equalProperties);
|
||||||
|
|
||||||
|
let map: Map<string, TablePropertiesMapEntry> = new Map();
|
||||||
|
unequalProperties.forEach((v, k) => {
|
||||||
|
map.set(k, v);
|
||||||
|
});
|
||||||
|
|
||||||
|
equalProperties.forEach((v, k) => {
|
||||||
|
map.set(k, v);
|
||||||
|
});
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
public sortPropertiesReverseAlphabetically(props: Map<string, TablePropertiesMapEntry>): Map<string, TablePropertiesMapEntry> {
|
public sortPropertiesReverseAlphabetically(props: Map<string, TablePropertiesMapEntry>): Map<string, TablePropertiesMapEntry> {
|
||||||
return new Map([...props.entries()].sort((a, b) => {
|
return new Map([...props.entries()].sort((a, b) => {
|
||||||
if (!a[1]?.displayOrder && !b[1]?.displayOrder) {
|
if (!a[1]?.displayOrder && !b[1]?.displayOrder) {
|
||||||
@@ -244,13 +398,13 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
|
|
||||||
switch (this.sortType) {
|
switch (this.sortType) {
|
||||||
case PropertiesSortType.DisplayOrder:
|
case PropertiesSortType.DisplayOrder:
|
||||||
propertiesMap = this.sortPropertiesByImportance(propertiesMap);
|
propertiesMap = this.sortPropertiesByDisplayValueEquivalency(propertiesMap, this.sortPropertiesByImportance);
|
||||||
break;
|
break;
|
||||||
case PropertiesSortType.Alphabetical:
|
case PropertiesSortType.Alphabetical:
|
||||||
propertiesMap = this.sortPropertiesAlphabetically(propertiesMap);
|
propertiesMap = this.sortPropertiesByDisplayValueEquivalency(propertiesMap, this.sortPropertiesAlphabetically);
|
||||||
break;
|
break;
|
||||||
case PropertiesSortType.ReverseAlphabetical:
|
case PropertiesSortType.ReverseAlphabetical:
|
||||||
propertiesMap = this.sortPropertiesReverseAlphabetically(propertiesMap);
|
propertiesMap = this.sortPropertiesByDisplayValueEquivalency(propertiesMap, this.sortPropertiesReverseAlphabetically);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,37 +417,56 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
const primaryProp = v.primaryProp;
|
const primaryProp = v.primaryProp;
|
||||||
const secondaryProp = v.secondaryProp;
|
const secondaryProp = v.secondaryProp;
|
||||||
let diffIconClass = '';
|
let diffIconClass = '';
|
||||||
|
|
||||||
if (primaryProp && secondaryProp) {
|
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) {
|
if (v.primaryProp.displayValue !== v.secondaryProp.displayValue) {
|
||||||
switch (v.primaryProp.dataType) {
|
switch (v.primaryProp.dataType) {
|
||||||
case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.Boolean:
|
case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.Boolean:
|
||||||
diffIconClass = executionPlanComparisonPropertiesDifferent;
|
diffIcon.iconClass = executionPlanComparisonPropertiesDifferent;
|
||||||
|
diffIcon.title = notEqualTitle;
|
||||||
break;
|
break;
|
||||||
case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.Number:
|
case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.Number:
|
||||||
diffIconClass = (parseFloat(v.primaryProp.displayValue) > parseFloat(v.secondaryProp.displayValue)) ? executionPlanComparisonPropertiesDownArrow : executionPlanComparisonPropertiesUpArrow;
|
diffIcon = (parseFloat(v.primaryProp.displayValue) > parseFloat(v.secondaryProp.displayValue))
|
||||||
|
? { iconClass: Codicon.chevronRight.classNames, title: greaterThanTitle }
|
||||||
|
: { iconClass: Codicon.chevronLeft.classNames, title: lessThanTitle };
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.String:
|
case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.String:
|
||||||
diffIconClass = executionPlanComparisonPropertiesDifferent;
|
diffIcon.iconClass = executionPlanComparisonPropertiesDifferent;
|
||||||
|
diffIcon.title = notEqualTitle;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
diffIconClass = executionPlanComparisonPropertiesDifferent;
|
diffIcon.iconClass = executionPlanComparisonPropertiesDifferent;
|
||||||
|
diffIcon.title = notEqualTitle;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
row['primary'] = {
|
row['primary'] = {
|
||||||
text: removeLineBreaks(v.primaryProp.displayValue, ' ')
|
text: removeLineBreaks(v.primaryProp.displayValue, ' ')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
row['icon'] = {
|
||||||
|
iconCssClass: diffIcon.iconClass,
|
||||||
|
title: diffIcon.title
|
||||||
|
};
|
||||||
|
|
||||||
row['secondary'] = {
|
row['secondary'] = {
|
||||||
iconCssClass: diffIconClass,
|
|
||||||
title: removeLineBreaks(v.secondaryProp.displayValue, ' ')
|
title: removeLineBreaks(v.secondaryProp.displayValue, ' ')
|
||||||
};
|
};
|
||||||
|
|
||||||
if ((primaryProp && !isString(primaryProp.value)) || (secondaryProp && !isString(secondaryProp.value))) {
|
if ((primaryProp && !isString(primaryProp.value)) || (secondaryProp && !isString(secondaryProp.value))) {
|
||||||
row['name'].iconCssClass += ` parent-row-styling`;
|
row['name'].iconCssClass += ` parent-row-styling`;
|
||||||
row['primary'].iconCssClass += ` parent-row-styling`;
|
row['primary'].iconCssClass += ` parent-row-styling`;
|
||||||
|
row['icon'].iconCssClass += 'parent-row-styling';
|
||||||
row['secondary'].iconCssClass += ` parent-row-styling`;
|
row['secondary'].iconCssClass += ` parent-row-styling`;
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
|
|
||||||
const topPropValue = isString(primaryProp.value) ? undefined : primaryProp.value;
|
const topPropValue = isString(primaryProp.value) ? undefined : primaryProp.value;
|
||||||
const bottomPropValue = isString(secondaryProp.value) ? undefined : secondaryProp.value;
|
const bottomPropValue = isString(secondaryProp.value) ? undefined : secondaryProp.value;
|
||||||
row['treeGridChildren'] = this.convertPropertiesToTableRows(topPropValue, bottomPropValue);
|
row['treeGridChildren'] = this.convertPropertiesToTableRows(topPropValue, bottomPropValue);
|
||||||
@@ -324,7 +497,28 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
return rows;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
set orientation(value: ExecutionPlanCompareOrientation) {
|
set orientation(value: ExecutionPlanCompareOrientation) {
|
||||||
@@ -365,3 +559,8 @@ interface TablePropertiesMapEntry {
|
|||||||
displayOrder: number,
|
displayOrder: number,
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DiffIcon {
|
||||||
|
iconClass: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
// Header container
|
// Header container
|
||||||
private _headerContainer: HTMLElement;
|
private _headerContainer: HTMLElement;
|
||||||
|
|
||||||
|
// Summary container
|
||||||
|
private _summaryContainer: HTMLElement;
|
||||||
|
|
||||||
// Properties actions
|
// Properties actions
|
||||||
private _headerActionsContainer!: HTMLElement;
|
private _headerActionsContainer!: HTMLElement;
|
||||||
private _headerActions: ActionBar;
|
private _headerActions: ActionBar;
|
||||||
@@ -110,6 +113,9 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
this._headerContainer = DOM.$('.header');
|
this._headerContainer = DOM.$('.header');
|
||||||
propertiesContent.appendChild(this._headerContainer);
|
propertiesContent.appendChild(this._headerContainer);
|
||||||
|
|
||||||
|
this._summaryContainer = DOM.$('.summary');
|
||||||
|
propertiesContent.appendChild(this._summaryContainer);
|
||||||
|
|
||||||
this._searchAndActionBarContainer = DOM.$('.search-action-bar-container');
|
this._searchAndActionBarContainer = DOM.$('.search-action-bar-container');
|
||||||
propertiesContent.appendChild(this._searchAndActionBarContainer);
|
propertiesContent.appendChild(this._searchAndActionBarContainer);
|
||||||
|
|
||||||
@@ -222,6 +228,10 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
this._headerContainer.appendChild(c);
|
this._headerContainer.appendChild(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setSummary(c: HTMLElement): void {
|
||||||
|
this._summaryContainer.appendChild(c);
|
||||||
|
}
|
||||||
|
|
||||||
public set tableHeight(value: number) {
|
public set tableHeight(value: number) {
|
||||||
if (this.tableHeight !== value) {
|
if (this.tableHeight !== value) {
|
||||||
this._tableHeight = value;
|
this._tableHeight = value;
|
||||||
@@ -264,6 +274,7 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
private resizeTable(): void {
|
private resizeTable(): void {
|
||||||
const spaceOccupied = (this._titleBarContainer.getBoundingClientRect().height
|
const spaceOccupied = (this._titleBarContainer.getBoundingClientRect().height
|
||||||
+ this._headerContainer.getBoundingClientRect().height
|
+ this._headerContainer.getBoundingClientRect().height
|
||||||
|
+ this._summaryContainer.getBoundingClientRect().height
|
||||||
+ this._headerActionsContainer.getBoundingClientRect().height);
|
+ this._headerActionsContainer.getBoundingClientRect().height);
|
||||||
|
|
||||||
this.tableHeight = (this._parentContainer.getBoundingClientRect().height - spaceOccupied - 15);
|
this.tableHeight = (this._parentContainer.getBoundingClientRect().height - spaceOccupied - 15);
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: auto;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .eps-container .ep-properties-different,
|
.vs-dark .eps-container .ep-properties-different,
|
||||||
@@ -607,9 +607,18 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: auto;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eps-container .comparison-editor .plan-comparison-container .properties .properties-content .table-container .table .monaco-table .ui-widget .slick-viewport .grid-canvas .ui-widget-content.slick-row .slick-cell .grid-cell-value-container.icon.codicon.slick-icon-cell-content.codicon-chevron-left,
|
||||||
|
.eps-container .comparison-editor .plan-comparison-container .properties .properties-content .table-container .table .monaco-table .ui-widget .slick-viewport .grid-canvas .ui-widget-content.slick-row .slick-cell .grid-cell-value-container.icon.codicon.slick-icon-cell-content.codicon-chevron-right {
|
||||||
|
background-size: 16px 16px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-left: -28px;
|
||||||
|
}
|
||||||
|
|
||||||
.eps-container .ep-properties-down-arrow {
|
.eps-container .ep-properties-down-arrow {
|
||||||
background-image: url(../images/actionIcons/downArrow.svg);
|
background-image: url(../images/actionIcons/downArrow.svg);
|
||||||
@@ -617,7 +626,7 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: auto;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .eps-container .ep-properties-down-arrow,
|
.vs-dark .eps-container .ep-properties-down-arrow,
|
||||||
@@ -627,7 +636,7 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: auto;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eps-container .ep-properties-up-arrow {
|
.eps-container .ep-properties-up-arrow {
|
||||||
@@ -636,7 +645,7 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: auto;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .eps-container .ep-properties-up-arrow,
|
.vs-dark .eps-container .ep-properties-up-arrow,
|
||||||
@@ -646,7 +655,7 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: auto;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eps-container .ep-top-operations {
|
.eps-container .ep-top-operations {
|
||||||
|
|||||||
Reference in New Issue
Block a user