diff --git a/src/sql/base/browser/ui/taskbar/actionbar.ts b/src/sql/base/browser/ui/taskbar/actionbar.ts index aef0cca06e..f132c5b7cf 100644 --- a/src/sql/base/browser/ui/taskbar/actionbar.ts +++ b/src/sql/base/browser/ui/taskbar/actionbar.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/actionBar'; import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/sql/base/browser/ui/taskbar/media/actionBar.css b/src/sql/base/browser/ui/taskbar/media/actionBar.css new file mode 100644 index 0000000000..a9149c21b4 --- /dev/null +++ b/src/sql/base/browser/ui/taskbar/media/actionBar.css @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-action-bar.vertical .action-item .action-label.separator { + width: 20px; + height: 1px; + margin: 5px 2px !important; +} diff --git a/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts b/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts index 3c42cedcb4..782600e188 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts @@ -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]; diff --git a/src/sql/workbench/contrib/executionPlan/browser/constants.ts b/src/sql/workbench/contrib/executionPlan/browser/constants.ts index c6838e3de6..d3a864faa0 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/constants.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/constants.ts @@ -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...'); diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts index 0fa92f2a05..d3bcf2ad1a 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts @@ -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; } diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts index 4bc4520b57..9799751c0c 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts @@ -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 = {}; 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 = 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; } diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesView.ts index 499d5b4378..80e9e0bb97 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesView.ts @@ -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 = {}; 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; } }); diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts index c4e4f9fc17..7570e5063b 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts @@ -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; + 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(); - 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(); 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[], 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}; + } + `); + } }); diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts index d8d9528c0b..312d042dae 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts @@ -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'), diff --git a/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/customZoom.svg b/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/customZoom.svg index 3020c8832e..a2c800b7eb 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/customZoom.svg +++ b/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/customZoom.svg @@ -1,23 +1,65 @@ - + - -