Execution Plan Filtering and UI improvements (#20631)

* Fixing execution plan stuff

* Adding filter to top operations

* Flipped custom zoom icon

* changing keys

* Moving constants to a file

* Search properties

* Making logic concise
This commit is contained in:
Aasim Khan
2022-09-23 12:57:30 -07:00
committed by GitHub
parent c7eefb28cb
commit fd8993134f
13 changed files with 421 additions and 101 deletions

View File

@@ -70,10 +70,13 @@ export class AzdataGraphView {
this.onElementSelected = this._onElementSelectedEmitter.event;
this._diagram.graph.getSelectionModel().addListener('change', (sender, evt) => {
if (evt.properties?.removed) {
if (this._cellInFocus?.id === evt.properties.removed[0].id) {
if (this._cellInFocus?.id === evt.properties.removed[0]?.id) {
return;
}
const newSelection = evt.properties.removed[0];
if (!newSelection) {
return;
}
this._onElementSelectedEmitter.fire(this.getElementById(newSelection.id));
this.centerElement(this.getElementById(newSelection.id));
this._cellInFocus = evt.properties.removed[0];

View File

@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
let imageBasePath = require.toUrl('./images/icons/');
export let executionPlanNodeIconPaths =
{
@@ -366,3 +368,8 @@ export const polygonFillColor: string[] = [
`rgba(70, 104, 197, 0.1)`, // "f2 blue purple"
`rgba(226, 229, 132, 0.1)`, // "j2 khaki"
];
//constant strings
export const propertiesSearchDescription = localize('ep.propertiesSearchDescription', 'Search properties table');
export const topOperationsSearchDescription = localize('ep.topOperationsSearchDescription', 'Search top operations');
export const searchPlaceholder = localize('ep.searchPlaceholder', 'Filter for any field...');

View File

@@ -352,7 +352,7 @@ export class ExecutionPlanComparisonEditorView {
if (this.activeBottomPlanDiagram) {
const element = this.activeBottomPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
if (this.activeBottomPlanDiagram.getSelectedElement() && similarNode.matchingNodesId.find(m => this.activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
if (similarNode.matchingNodesId.find(m => this.activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
return;
}

View File

@@ -15,7 +15,7 @@ import { InternalExecutionPlanElement } from 'sql/workbench/contrib/executionPla
import { executionPlanComparisonPropertiesDifferent, executionPlanComparisonPropertiesUpArrow, executionPlanComparisonPropertiesDownArrow } from 'sql/workbench/contrib/executionPlan/browser/constants';
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
import { TextWithIconColumn } from 'sql/base/browser/ui/table/plugins/textWithIconColumn';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export enum ExecutionPlanCompareOrientation {
@@ -56,9 +56,10 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
parentContainer: HTMLElement,
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService
@IContextMenuService contextMenuService: IContextMenuService,
@IContextViewService contextViewService: IContextViewService
) {
super(parentContainer, themeService, instantiationService, contextMenuService);
super(parentContainer, themeService, instantiationService, contextMenuService, contextViewService);
this._model = <ExecutionPlanComparisonPropertiesViewModel>{};
this._parentContainer.style.display = 'none';
const header = DOM.$('.compare-operation-name');
@@ -133,8 +134,7 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
if (this._model.secondaryElement?.properties) {
secondaryProps = this._model.secondaryElement.properties;
}
this.populateTable(columns, this.convertPropertiesToTableRows(primaryProps, secondaryProps, -1, 0));
this.populateTable(columns, this.convertPropertiesToTableRows(primaryProps, secondaryProps));
}
private getPropertyTableColumns() {
@@ -212,7 +212,8 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
}));
}
private convertPropertiesToTableRows(primaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[], secondaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
private convertPropertiesToTableRows(primaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[], secondaryNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[]): { [key: string]: string }[] {
const rows: { [key: string]: string }[] = [];
let propertiesMap: Map<string, TablePropertiesMapEntry> = new Map();
if (primaryNode) {
@@ -258,7 +259,6 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
row['name'] = {
text: k
};
row['parent'] = parentIndex;
const primaryProp = v.primaryProp;
const secondaryProp = v.secondaryProp;
@@ -294,13 +294,10 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
row['secondary'].iconCssClass += ` parent-row-styling`;
}
rows.push(row);
if (!isString(primaryProp.value) && !isString(secondaryProp.value)) {
this.convertPropertiesToTableRows(primaryProp.value, secondaryProp.value, rows.length - 1, indent + 2, rows);
} else if (isString(primaryProp?.value) && !isString(secondaryProp.value)) {
this.convertPropertiesToTableRows(undefined, secondaryProp.value, rows.length - 1, indent + 2, rows);
} else if (!isString(primaryProp.value) && !isString(secondaryProp.value)) {
this.convertPropertiesToTableRows(primaryProp.value, undefined, rows.length - 1, indent + 2, rows);
}
const topPropValue = isString(primaryProp.value) ? undefined : primaryProp.value;
const bottomPropValue = isString(secondaryProp.value) ? undefined : secondaryProp.value;
row['treeGridChildren'] = this.convertPropertiesToTableRows(topPropValue, bottomPropValue);
} else if (primaryProp && !secondaryProp) {
row['displayOrder'] = v.primaryProp.displayOrder;
row['primary'] = {
@@ -310,7 +307,7 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
if (!isString(primaryProp.value)) {
row['name'].iconCssClass += ` parent-row-styling`;
row['primary'].iconCssClass += ` parent-row-styling`;
this.convertPropertiesToTableRows(primaryProp.value, undefined, rows.length - 1, indent + 2, rows);
row['treeGridChildren'] = this.convertPropertiesToTableRows(primaryProp.value, undefined);
}
} else if (!primaryProp && secondaryProp) {
row['displayOrder'] = v.secondaryProp.displayOrder;
@@ -322,12 +319,11 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
if (!isString(secondaryProp.value)) {
row['name'].iconCssClass += ` parent-row-styling`;
row['secondary'].iconCssClass += ` parent-row-styling`;
this.convertPropertiesToTableRows(undefined, secondaryProp.value, rows.length - 1, indent + 2, rows);
row['treeGridChildren'] = this.convertPropertiesToTableRows(undefined, secondaryProp.value);
}
}
});
return rows;
}

View File

@@ -11,7 +11,7 @@ import { removeLineBreaks } from 'sql/base/common/strings';
import { isString } from 'vs/base/common/types';
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
import { ExecutionPlanPropertiesViewBase, PropertiesSortType } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase {
@@ -24,8 +24,9 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService,
@IContextViewService contextViewService: IContextViewService
) {
super(parentContainer, themeService, instantiationService, contextMenuService);
super(parentContainer, themeService, instantiationService, contextMenuService, contextViewService);
this._model = <ExecutionPlanPropertiesView>{};
this._operationName = DOM.$('h3');
this._operationName.classList.add('operation-name');
@@ -108,14 +109,38 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
}
];
this.populateTable(columns, this.convertModelToTableRows(this._model.graphElement.properties, -1, 0));
this.populateTable(columns, this.convertPropertiesToTableRows(this._model.graphElement?.properties));
}
private convertModelToTableRows(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
if (!props) {
return rows;
private convertPropertiesToTableRows(properties: azdata.executionPlan.ExecutionPlanGraphElementProperty[]): Slick.SlickData[] {
if (!properties) {
return [];
}
const sortedProperties = this.sortProperties(properties);
const rows: Slick.SlickData[] = [];
sortedProperties.forEach((property, index) => {
let row = {};
row['name'] = property.name;
if (!isString(property.value)) {
// Styling values in the parent row differently to make them more apparent and standout compared to the rest of the cells.
row['name'] = {
text: row['name']
};
row['value'] = {
text: removeLineBreaks(property.displayValue, ' ')
};
row['tootltip'] = property.displayValue;
row['treeGridChildren'] = this.convertPropertiesToTableRows(property.value);
} else {
row['value'] = removeLineBreaks(property.displayValue, ' ');
row['tooltip'] = property.displayValue;
}
rows.push(row);
});
return rows;
}
private sortProperties(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[]): azdata.executionPlan.ExecutionPlanGraphElementProperty[] {
switch (this.sortType) {
case PropertiesSortType.DisplayOrder:
props = this.sortPropertiesByImportance(props);
@@ -127,6 +152,13 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
props = this.sortPropertiesReverseAlphabetically(props);
break;
}
return props;
}
private convertModelToTableRows(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[] | undefined, parentIndex: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
if (!props) {
return rows;
}
props.forEach((p, i) => {
let row = {};
@@ -142,9 +174,11 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
text: removeLineBreaks(p.displayValue, ' ')
};
row['tootltip'] = p.displayValue;
this.convertModelToTableRows(p.value, rows.length - 1, indent + 2, rows);
this.convertModelToTableRows(p.value, rows.length - 1, rows);
} else {
row['value'] = removeLineBreaks(p.displayValue, ' ');
row['value'] = {
text: removeLineBreaks(p.displayValue, ' ')
};
row['tooltip'] = p.displayValue;
}
});

View File

@@ -10,26 +10,36 @@ import { localize } from 'vs/nls';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { sortAlphabeticallyIconClassNames, sortByDisplayOrderIconClassNames, sortReverseAlphabeticallyIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
import { propertiesSearchDescription, searchIconClassNames, searchPlaceholder, sortAlphabeticallyIconClassNames, sortByDisplayOrderIconClassNames, sortReverseAlphabeticallyIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/common/constants';
import { contrastBorder, listHoverBackground, listInactiveSelectionBackground } from 'vs/platform/theme/common/colorRegistry';
import { contrastBorder, inputBackground, listHoverBackground, listInactiveSelectionBackground } from 'vs/platform/theme/common/colorRegistry';
import { TreeGrid } from 'sql/base/browser/ui/table/treeGrid';
import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { isString } from 'vs/base/common/types';
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { deepClone } from 'vs/base/common/objects';
import { Disposable } from 'vs/base/common/lifecycle';
export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLayoutProvider {
export abstract class ExecutionPlanPropertiesViewBase extends Disposable implements IVerticalSashLayoutProvider {
// Title bar with close button action
private _titleBarContainer!: HTMLElement;
private _titleBarTextContainer!: HTMLElement;
private _titleBarActionsContainer!: HTMLElement;
private _titleActions: ActionBar;
// search bar and functrion
private _propertiesSearchInput: InputBox;
private _propertiesSearchInputContainer: HTMLElement;
private _searchAndActionBarContainer: HTMLElement;
// Header container
private _headerContainer: HTMLElement;
@@ -50,22 +60,25 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
private _selectionModel: CellSelectionModel<Slick.SlickData>;
private _tableData: Slick.SlickData[];
constructor(
public _parentContainer: HTMLElement,
private _themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService private _contextMenuService: IContextMenuService
@IContextMenuService private _contextMenuService: IContextMenuService,
@IContextViewService private _contextViewService: IContextViewService
) {
super();
const sashContainer = DOM.$('.properties-sash');
this._parentContainer.appendChild(sashContainer);
this.resizeSash = new Sash(sashContainer, this, { orientation: Orientation.VERTICAL, size: 3 });
this.resizeSash = this._register(new Sash(sashContainer, this, { orientation: Orientation.VERTICAL, size: 3 }));
let originalWidth = 0;
this.resizeSash.onDidStart((e: ISashEvent) => {
this._register(this.resizeSash.onDidStart((e: ISashEvent) => {
originalWidth = this._parentContainer.clientWidth;
});
this.resizeSash.onDidChange((evt: ISashEvent) => {
}));
this._register(this.resizeSash.onDidChange((evt: ISashEvent) => {
const change = evt.startX - evt.currentX;
const newWidth = originalWidth + change;
if (newWidth < 200) {
@@ -73,9 +86,7 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
}
this._parentContainer.style.flex = `0 0 ${newWidth}px`;
propertiesContent.style.width = `${newWidth}px`;
});
this.resizeSash.onDidEnd(() => {
});
}));
const propertiesContent = DOM.$('.properties-content');
this._parentContainer.appendChild(propertiesContent);
@@ -91,21 +102,35 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
this._titleBarActionsContainer = DOM.$('.action-bar');
this._titleBarContainer.appendChild(this._titleBarActionsContainer);
this._titleActions = new ActionBar(this._titleBarActionsContainer, {
this._titleActions = this._register(new ActionBar(this._titleBarActionsContainer, {
orientation: ActionsOrientation.HORIZONTAL, context: this
});
}));
this._titleActions.pushAction([new ClosePropertyViewAction()], { icon: true, label: false });
this._headerContainer = DOM.$('.header');
propertiesContent.appendChild(this._headerContainer);
this._searchAndActionBarContainer = DOM.$('.search-action-bar-container');
propertiesContent.appendChild(this._searchAndActionBarContainer);
this._headerActionsContainer = DOM.$('.table-action-bar');
propertiesContent.appendChild(this._headerActionsContainer);
this._headerActions = new ActionBar(this._headerActionsContainer, {
this._searchAndActionBarContainer.appendChild(this._headerActionsContainer);
this._headerActions = this._register(new ActionBar(this._headerActionsContainer, {
orientation: ActionsOrientation.HORIZONTAL, context: this
});
}));
this._headerActions.pushAction([new SortPropertiesByDisplayOrderAction(), new SortPropertiesAlphabeticallyAction(), new SortPropertiesReverseAlphabeticallyAction()], { icon: true, label: false });
this._propertiesSearchInputContainer = DOM.$('.table-search');
this._propertiesSearchInputContainer.classList.add('codicon', searchIconClassNames);
this._propertiesSearchInput = this._register(new InputBox(this._propertiesSearchInputContainer, this._contextViewService, {
ariaDescription: propertiesSearchDescription,
placeholder: searchPlaceholder
}));
this._propertiesSearchInput.element.classList.add('codicon', searchIconClassNames);
this._searchAndActionBarContainer.appendChild(this._propertiesSearchInputContainer);
this._register(this._propertiesSearchInput.onDidChange(e => {
this.searchTable(e);
}));
this._tableContainer = DOM.$('.table-container');
propertiesContent.appendChild(this._tableContainer);
@@ -115,7 +140,7 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
this._selectionModel = new CellSelectionModel<Slick.SlickData>();
this._tableComponent = new TreeGrid(table, {
this._tableComponent = this._register(new TreeGrid(table, {
columns: []
}, {
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
@@ -123,7 +148,7 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
defaultColumnWidth: 120,
editable: true,
autoEdit: false
});
}));
attachTableStyler(this._tableComponent, this._themeService);
this._tableComponent.setSelectionModel(this._selectionModel);
@@ -131,20 +156,20 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
this._instantiationService.createInstance(CopyTableData),
];
this._tableComponent.onContextMenu(e => {
this._register(this._tableComponent.onContextMenu(e => {
this._contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => contextMenuAction,
getActionsContext: () => this.getCopyString()
});
});
}));
let copyHandler = new CopyKeybind<any>();
this._tableComponent.registerPlugin(copyHandler);
copyHandler.onCopy(e => {
this._register(copyHandler.onCopy(e => {
this._instantiationService.createInstance(CopyTableData).run(this.getCopyString());
});
}));
new ResizeObserver((e) => {
this.resizeSash.layout();
@@ -226,7 +251,9 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
public populateTable(columns: Slick.Column<Slick.SlickData>[], data: { [key: string]: string }[]) {
this._tableComponent.columns = columns;
this._tableContainer.scrollTo(0, 0);
this._tableComponent.setData(data);
this._tableData = data;
this._propertiesSearchInput.value = '';
this._tableComponent.setData(this.flattenTableData(data, -1));
this.resizeTable();
}
@@ -245,6 +272,68 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
this._tableComponent.resizeCanvas();
}
private searchTable(searchString: string): void {
if (searchString === '') {
this._tableComponent.setData(this.flattenTableData(this._tableData, -1));
} else {
this._tableComponent.setData(
this.flattenTableData(
this.searchNestedTableData(searchString, this._tableData).data,
-1)
);
}
this._tableComponent.rerenderGrid();
}
private searchNestedTableData(search: string, data: Slick.SlickData[]): { include: boolean, data: Slick.SlickData[] } {
let resultData: Slick.SlickData[] = [];
data.forEach(dataRow => {
let includeRow = false;
const columns = this._tableComponent.grid.getColumns();
for (let i = 0; i < columns.length; i++) {
let dataValue = '';
let rawDataValue = dataRow[columns[i].field];
if (isString(rawDataValue)) {
dataValue = rawDataValue;
} else if (rawDataValue !== undefined) {
dataValue = rawDataValue.text ?? rawDataValue.title;
}
if (dataValue.toLowerCase().includes(search.toLowerCase())) {
includeRow = true;
break;
}
}
const rowClone = deepClone(dataRow);
if (rowClone['treeGridChildren'] !== undefined) {
const result = this.searchNestedTableData(search, rowClone['treeGridChildren']);
rowClone['treeGridChildren'] = result.data;
includeRow = includeRow || result.include;
}
if (includeRow) {
if (rowClone['treeGridChildren'] !== undefined) {
rowClone['expanded'] = true;
}
resultData.push(rowClone);
}
});
return { include: resultData.length > 0, data: resultData };
}
private flattenTableData(nestedData: Slick.SlickData[], parentIndex: number, rows: Slick.SlickData[] = []): Slick.SlickData[] {
if (nestedData === undefined || nestedData.length === 0) {
return rows;
}
nestedData.forEach((dataRow) => {
rows.push(dataRow);
dataRow['parent'] = parentIndex;
if (dataRow['treeGridChildren']) {
this.flattenTableData(dataRow['treeGridChildren'], rows.length - 1, rows);
}
});
return rows;
}
}
@@ -342,6 +431,15 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
}
`);
}
const searchBarBackground = theme.getColor(inputBackground);
if (inputBackground) {
collector.addRule(`
.eps-container .properties .search-action-bar-container .table-search {
background-color: ${searchBarBackground};
}
`);
}
});

View File

@@ -160,10 +160,12 @@ export class ExecutionPlanView implements ISashLayoutProvider {
new SavePlanFile(),
new OpenPlanFile(),
this._instantiationService.createInstance(OpenQueryAction, 'ActionBar'),
new Separator(),
this._instantiationService.createInstance(ZoomInAction, 'ActionBar'),
this._instantiationService.createInstance(ZoomOutAction, 'ActionBar'),
this._instantiationService.createInstance(ZoomToFitAction, 'ActionBar'),
this._instantiationService.createInstance(CustomZoomAction, 'ActionBar'),
new Separator(),
this._instantiationService.createInstance(SearchNodeAction, 'ActionBar'),
this._instantiationService.createInstance(PropertiesAction, 'ActionBar'),
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ActionBar'),

View File

@@ -1,23 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 16 16"
style="enable-background:new 0 0 16 16;"
xml:space="preserve"
sodipodi:docname="customZoom.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs13" /><sodipodi:namedview
id="namedview11"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="14.75"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-width="1792"
inkscape:window-height="1049"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<style
type="text/css"
id="style2">
.st0{fill:#F6F6F6;fill-opacity:0;}
.st1{fill:#F6F6F6;}
.st2{fill:#424242;}
.st3{fill:#F0EFF1;}
</style>
<g id="canvas">
<path class="st0" d="M16,16H0V0h16V16z"/>
<g
id="canvas"
transform="matrix(-1,0,0,1,15.95,0)">
<path
class="st0"
d="M 16,16 H 0 V 0 h 16 z"
id="path4" />
</g>
<path id="outline" class="st1" d="M16,5.8c0-3.2-2.6-5.7-5.8-5.8C7,0,4.4,2.6,4.3,5.8c0,1,0.3,2,0.8,2.9l-4.7,4.7
c-0.6,0.6-0.6,1.5,0,2.1c0.6,0.6,1.5,0.6,2.1,0l4.7-4.7c0.4,0.3,0.9,0.4,1.3,0.6l-0.8,0.8L7,16h1.4l2.4-0.8l5.2-5.1l-1-1
C15.6,8.1,16,7,16,5.8z"/>
<path id="iconBg" class="st2" d="M10.4,9.6c-0.1,0-0.2,0-0.2,0C8,9.7,6.3,8,6.3,5.8C6.3,3.7,8,2,10.2,2C12.3,2,14,3.7,14,5.8v0
c0,0.6-0.2,1.2-0.5,1.8l0.7,0.7c0.4-0.7,0.7-1.6,0.7-2.5c0-2.6-2.1-4.7-4.8-4.8v0C7.5,1,5.3,3.2,5.3,5.8c0,1.1,0.4,2.2,1.1,3
l-5.3,5.3c-0.2,0.2-0.2,0.5,0,0.7c0.2,0.2,0.5,0.2,0.7,0l5.3-5.3c0.7,0.6,1.5,0.9,2.3,1L10.4,9.6z M8.2,15l2.1-0.7l-1.6-1.6L8.2,15z
M12.3,9.2l1.6,1.6l0.7-0.7L13,8.5L12.3,9.2z M9.5,12l1.6,1.6l2.1-2.1l-1.6-1.6L9.5,12z"/>
<path id="iconFg" class="st3" d="M10.2,9.7c0.1,0,0.2,0,0.2,0L13,7.1l0.6,0.6C13.8,7.1,14,6.5,14,5.8C14,3.7,12.3,2,10.2,2
c0,0,0,0,0,0C8,2,6.3,3.7,6.3,5.8C6.3,8,8,9.7,10.2,9.7z"/>
<path
id="outline"
class="st1"
d="m -0.05,5.8 c 0,-3.2 2.6,-5.7 5.8,-5.8 3.2,0 5.8,2.6 5.9,5.8 0,1 -0.3,2 -0.8,2.9 l 4.7,4.7 c 0.6,0.6 0.6,1.5 0,2.1 -0.6,0.6 -1.5,0.6 -2.1,0 l -4.7,-4.7 c -0.4,0.3 -0.9,0.4 -1.3,0.6 l 0.8,0.8 0.7,3.8 h -1.4 l -2.4,-0.8 -5.2,-5.1 1,-1 c -0.6,-1 -1,-2.1 -1,-3.3 z" />
<path
id="iconBg"
class="st2"
d="m 5.55,9.6 c 0.1,0 0.2,0 0.2,0 C 7.95,9.7 9.65,8 9.65,5.8 9.65,3.7 7.95,2 5.75,2 3.65,2 1.95,3.7 1.95,5.8 v 0 c 0,0.6 0.2,1.2 0.5,1.8 L 1.75,8.3 C 1.35,7.6 1.05,6.7 1.05,5.8 1.05,3.2 3.15,1.1 5.85,1 v 0 c 2.6,0 4.8,2.2 4.8,4.8 0,1.1 -0.4,2.2 -1.1,3 l 5.3,5.3 c 0.2,0.2 0.2,0.5 0,0.7 -0.2,0.2 -0.5,0.2 -0.7,0 L 8.85,9.5 c -0.7,0.6 -1.5,0.9 -2.3,1 z m 2.2,5.4 -2.1,-0.7 1.6,-1.6 z m -4.1,-5.8 -1.6,1.6 -0.7,-0.7 1.6,-1.6 z m 2.8,2.8 -1.6,1.6 -2.1,-2.1 1.6,-1.6 z" />
<path
id="iconFg"
class="st3"
d="m 5.75,9.7 c -0.1,0 -0.2,0 -0.2,0 L 2.95,7.1 2.35,7.7 C 2.15,7.1 1.95,6.5 1.95,5.8 1.95,3.7 3.65,2 5.75,2 c 0,0 0,0 0,0 2.2,0 3.9,1.7 3.9,3.8 0,2.2 -1.7,3.9 -3.9,3.9 z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,23 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 16 16"
style="enable-background:new 0 0 16 16;"
xml:space="preserve"
sodipodi:docname="customZoomDark.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs148" /><sodipodi:namedview
id="namedview146"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="14.75"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-width="1792"
inkscape:window-height="1049"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<style
type="text/css"
id="style137">
.st0{fill:#F6F6F6;fill-opacity:0;}
.st1{fill:#0000;}
.st2{fill:#fff;}
.st3{fill:#0000;}
</style>
<g id="canvas">
<path class="st0" d="M16,16H0V0h16V16z"/>
<g
id="canvas"
transform="matrix(-1,0,0,1,15.95,0)">
<path
class="st0"
d="M 16,16 H 0 V 0 h 16 z"
id="path139" />
</g>
<path id="outline" class="st1" d="M16,5.8c0-3.2-2.6-5.7-5.8-5.8C7,0,4.4,2.6,4.3,5.8c0,1,0.3,2,0.8,2.9l-4.7,4.7
c-0.6,0.6-0.6,1.5,0,2.1c0.6,0.6,1.5,0.6,2.1,0l4.7-4.7c0.4,0.3,0.9,0.4,1.3,0.6l-0.8,0.8L7,16h1.4l2.4-0.8l5.2-5.1l-1-1
C15.6,8.1,16,7,16,5.8z"/>
<path id="iconBg" class="st2" d="M10.4,9.6c-0.1,0-0.2,0-0.2,0C8,9.7,6.3,8,6.3,5.8C6.3,3.7,8,2,10.2,2C12.3,2,14,3.7,14,5.8v0
c0,0.6-0.2,1.2-0.5,1.8l0.7,0.7c0.4-0.7,0.7-1.6,0.7-2.5c0-2.6-2.1-4.7-4.8-4.8v0C7.5,1,5.3,3.2,5.3,5.8c0,1.1,0.4,2.2,1.1,3
l-5.3,5.3c-0.2,0.2-0.2,0.5,0,0.7c0.2,0.2,0.5,0.2,0.7,0l5.3-5.3c0.7,0.6,1.5,0.9,2.3,1L10.4,9.6z M8.2,15l2.1-0.7l-1.6-1.6L8.2,15z
M12.3,9.2l1.6,1.6l0.7-0.7L13,8.5L12.3,9.2z M9.5,12l1.6,1.6l2.1-2.1l-1.6-1.6L9.5,12z"/>
<path id="iconFg" class="st3" d="M10.2,9.7c0.1,0,0.2,0,0.2,0L13,7.1l0.6,0.6C13.8,7.1,14,6.5,14,5.8C14,3.7,12.3,2,10.2,2
c0,0,0,0,0,0C8,2,6.3,3.7,6.3,5.8C6.3,8,8,9.7,10.2,9.7z"/>
<path
id="outline"
class="st1"
d="m -0.05,5.8 c 0,-3.2 2.6,-5.7 5.8,-5.8 3.2,0 5.8,2.6 5.9,5.8 0,1 -0.3,2 -0.8,2.9 l 4.7,4.7 c 0.6,0.6 0.6,1.5 0,2.1 -0.6,0.6 -1.5,0.6 -2.1,0 l -4.7,-4.7 c -0.4,0.3 -0.9,0.4 -1.3,0.6 l 0.8,0.8 0.7,3.8 h -1.4 l -2.4,-0.8 -5.2,-5.1 1,-1 c -0.6,-1 -1,-2.1 -1,-3.3 z" />
<path
id="iconBg"
class="st2"
d="m 5.55,9.6 c 0.1,0 0.2,0 0.2,0 C 7.95,9.7 9.65,8 9.65,5.8 9.65,3.7 7.95,2 5.75,2 3.65,2 1.95,3.7 1.95,5.8 v 0 c 0,0.6 0.2,1.2 0.5,1.8 L 1.75,8.3 C 1.35,7.6 1.05,6.7 1.05,5.8 1.05,3.2 3.15,1.1 5.85,1 v 0 c 2.6,0 4.8,2.2 4.8,4.8 0,1.1 -0.4,2.2 -1.1,3 l 5.3,5.3 c 0.2,0.2 0.2,0.5 0,0.7 -0.2,0.2 -0.5,0.2 -0.7,0 L 8.85,9.5 c -0.7,0.6 -1.5,0.9 -2.3,1 z m 2.2,5.4 -2.1,-0.7 1.6,-1.6 z m -4.1,-5.8 -1.6,1.6 -0.7,-0.7 1.6,-1.6 z m 2.8,2.8 -1.6,1.6 -2.1,-2.1 1.6,-1.6 z" />
<path
id="iconFg"
class="st3"
d="m 5.75,9.7 c -0.1,0 -0.2,0 -0.2,0 L 2.95,7.1 2.35,7.7 C 2.15,7.1 1.95,6.5 1.95,5.8 1.95,3.7 3.65,2 5.75,2 c 0,0 0,0 0,0 2.2,0 3.9,1.7 3.9,3.8 0,2.2 -1.7,3.9 -3.9,3.9 z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -101,7 +101,7 @@ However we always want it to be the width of the container it is resizing.
/* execution plan header that contains the relative query cost, query statement and recommendations */
.eps-container .execution-plan .plan .header .query-row,
.top-operations-tab .top-operations-container .query-row{
.top-operations-tab .top-operations-container .query-row {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
@@ -187,6 +187,31 @@ However we always want it to be the width of the container it is resizing.
margin-top: 5px;
}
.eps-container .properties .search-action-bar-container .table-action-bar {
flex: auto
}
.eps-container .properties .search-action-bar-container .table-search {
flex: 1;
max-width: 300px;
}
.eps-container .properties .search-action-bar-container .table-search>div,
.top-operations-tab .top-operations-container .top-operations-header-search-bar>div {
background-position-y: center;
background-position-x: 3px;
}
.eps-container .properties .search-action-bar-container .table-search input,
.top-operations-tab .top-operations-container .top-operations-header-search-bar input {
margin-left: 20px;
width: calc(100% - 20px);
}
.eps-container .properties .search-action-bar-container {
display: flex;
}
/* Operation name styling in the properties view. */
.eps-container .properties .operation-name {
white-space: nowrap;
@@ -316,7 +341,8 @@ However we always want it to be the width of the container it is resizing.
}
.eps-container .ep-search-icon {
.eps-container .ep-search-icon,
.top-operations-tab .ep-search-icon {
background-image: url(../images/actionIcons/search.svg);
background-size: 16px 16px;
background-position: center;
@@ -324,7 +350,9 @@ However we always want it to be the width of the container it is resizing.
}
.vs-dark .eps-container .ep-search-icon,
.hc-black .eps-container .ep-search-icon {
.hc-black .eps-container .ep-search-icon,
.vs-dark .top-operations-tab .ep-search-icon,
.hc-black .top-operations-tab .ep-search-icon {
background-image: url(../images/actionIcons/searchDark.svg);
background-size: 16px 16px;
background-position: center;
@@ -799,21 +827,34 @@ However we always want it to be the width of the container it is resizing.
overflow: scroll;
}
.top-operations-tab .top-operations-container .top-operations-header {
display: flex;
flex-direction: row;
}
.top-operations-tab .top-operations-container .top-operations-header .top-operations-header-info {
flex: 1;
}
.top-operations-tab .top-operations-container .top-operations-header .top-operations-header-search-bar {
min-width: 300px;
}
.top-operations-tab .top-operations-container .table-container {
flex: 1;
height: calc(100% - 50px);
}
.graph-cell{
.graph-cell {
align-items: center;
position: relative;
width: fit-content;
height: 80px;
font-size: 10px;
font-family:'Monaco', 'Menlo', 'Consolas';
font-family: 'Monaco', 'Menlo', 'Consolas';
}
.graph-cell-body{
.graph-cell-body {
display: flex;
flex-direction: column;
align-items: center;
@@ -822,12 +863,12 @@ However we always want it to be the width of the container it is resizing.
outline: none !important;
}
.graph-cell-icon{
.graph-cell-icon {
background-position: center;
background-repeat: no-repeat;
background-size: cover;
width:30px;
height:30px;
width: 30px;
height: 30px;
align-self: center;
position: relative;
}
@@ -837,8 +878,8 @@ However we always want it to be the width of the container it is resizing.
background-position: center;
background-repeat: no-repeat;
background-size: cover;
width:12px;
height:12px;
width: 12px;
height: 12px;
bottom: 0;
right: -5px;
}
@@ -846,9 +887,10 @@ However we always want it to be the width of the container it is resizing.
.graph-cell .graph-cell-row-count {
position: absolute;
top: 30%;
left: calc(50% - 60px);
right: calc(50% + 17px);
margin-right: 3px;
min-width: 20px;
text-align: right;
text-align: left;
outline: none !important;
}
@@ -861,7 +903,7 @@ However we always want it to be the width of the container it is resizing.
right: 0px;
}
.graph-cell-cost{
.graph-cell-cost {
border-radius: 15px;
width: fit-content;
padding: 1px 8px 0px 8px;

View File

@@ -23,11 +23,13 @@ import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugi
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
import { listHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { Action } from 'vs/base/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ITableKeyboardEvent } from 'sql/base/browser/ui/table/interfaces';
import { Disposable } from 'vs/base/common/lifecycle';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { searchIconClassNames, searchPlaceholder, topOperationsSearchDescription } from 'sql/workbench/contrib/executionPlan/browser/constants';
const TABLE_SORT_COLUMN_KEY = 'tableCostColumnForSorting';
@@ -59,6 +61,7 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
@IThemeService private _themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@IContextViewService private _contextViewService: IContextViewService
) {
super();
}
@@ -145,13 +148,30 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
name: c,
field: c.toString(),
formatter: i === 0 ? hyperLinkFormatter : textFormatter,
sortable: true,
sortable: true
};
});
const topOperationContainer = DOM.$('.top-operations-container');
this._container.appendChild(topOperationContainer);
const header = this._instantiationService.createInstance(ExecutionPlanViewHeader, topOperationContainer, {
const headerContainer = DOM.$('.top-operations-header');
topOperationContainer.appendChild(headerContainer);
const headerInfoContainer = DOM.$('.top-operations-header-info');
headerContainer.appendChild(headerInfoContainer);
const headerSearchBarContainer = DOM.$('.top-operations-header-search-bar');
headerContainer.appendChild(headerSearchBarContainer);
headerContainer.classList.add('codicon', searchIconClassNames);
const topOperationsSearchInput = new InputBox(headerSearchBarContainer, this._contextViewService, {
ariaDescription: topOperationsSearchDescription,
placeholder: searchPlaceholder
});
topOperationsSearchInput.element.classList.add('codicon', searchIconClassNames);
const header = this._instantiationService.createInstance(ExecutionPlanViewHeader, headerInfoContainer, {
planIndex: index,
});
header.query = graph.query;
@@ -207,7 +227,7 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
columns: columns,
sorter: (args) => {
const column = args.sortCol.field;
dataMap.sort((a, b) => {
const sortedData = table.getData().getItems().sort((a, b) => {
let result = -1;
if (!a[column]) {
@@ -237,7 +257,7 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
}
return args.sortAsc ? result : -result;
});
table.setData(dataMap);
table.setData(sortedData);
}
}, {
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
@@ -268,6 +288,29 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
this._instantiationService.createInstance(SelectAll)
];
this._register(topOperationsSearchInput.onDidChange(e => {
const filter = e.toLowerCase();
if (filter) {
const filteredData = dataMap.filter(row => {
let includeRow = false;
for (let i = 0; i < columns.length; i++) {
const columnField = columns[i].field;
if (row[columnField]) {
const text = row[columnField].displayText ?? row[columnField].text;
if (text.toLowerCase().includes(filter)) {
includeRow = true;
}
}
}
return includeRow;
});
table.setData(filteredData);
} else {
table.setData(dataMap);
}
table.rerenderGrid();
}));
this._register(table.onKeyDown((evt: ITableKeyboardEvent) => {
if (evt.event.ctrlKey && (evt.event.key === 'a' || evt.event.key === 'A')) {
selectionModel.setSelectedRanges([new Slick.Range(0, 1, table.getData().getLength() - 1, table.columns.length - 1)]);