diff --git a/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts b/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts index 9ad51c7075..42ec761a64 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts @@ -138,6 +138,9 @@ export class AzdataGraphView { */ public zoomToFit(): void { this._diagram.zoomToFit(); + if (this.getZoomLevel() > 200) { + this.setZoomLevel(200); + } } /** diff --git a/src/sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput.ts b/src/sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput.ts index 59790d01a4..de34160b56 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput.ts @@ -13,7 +13,7 @@ import { ExecutionPlanComparisonEditorView } from 'sql/workbench/contrib/executi export class ExecutionPlanComparisonInput extends EditorInput { public static ID: string = 'workbench.editorinputs.compareExecutionPlanInput'; public static SCHEME: string = 'compareExecutionPlanInput'; - private readonly editorNamePrefix = localize('epCompare.editorName', "Compare Execution Plans"); + private readonly editorNamePrefix = localize('epCompare.editorName', "CompareExecutionPlans"); private _editorName: string; // Caching the views for faster tab switching @@ -27,11 +27,11 @@ export class ExecutionPlanComparisonInput extends EditorInput { // Getting name for the editor const existingNames = this._editorService.editors.map(editor => editor.getName()); - let i = 0; - this._editorName = `${this.editorNamePrefix} ${i}`; + let i = 1; + this._editorName = `${this.editorNamePrefix}_${i}`; while (existingNames.includes(this._editorName)) { i++; - this._editorName = `${this.editorNamePrefix} ${i}`; + this._editorName = `${this.editorNamePrefix}_${i}`; } } diff --git a/src/sql/workbench/contrib/executionPlan/browser/constants.ts b/src/sql/workbench/contrib/executionPlan/browser/constants.ts index a78a7eae4a..41052c6d46 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/constants.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/constants.ts @@ -271,6 +271,7 @@ export const openQueryIconClassNames = 'ep-open-query-icon'; export const openPlanFileIconClassNames = 'ep-open-plan-file-icon'; export const saveIconClassNames = 'ep-save-icon'; export const searchIconClassNames = 'ep-search-icon'; +export const filterIconClassNames = 'ep-filter-icon'; export const sortAlphabeticallyIconClassNames = 'ep-sort-alphabetically-icon'; export const sortReverseAlphabeticallyIconClassNames = 'ep-sort-reverse-alphabetically-icon'; export const sortByDisplayOrderIconClassNames = 'ep-sort-display-order-icon'; diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts index d3bcf2ad1a..c6de778867 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts @@ -19,7 +19,7 @@ import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticip import * as DOM from 'vs/base/browser/dom'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { localize } from 'vs/nls'; -import { addIconClassName, openPropertiesIconClassNames, polygonBorderColor, polygonFillColor, resetZoomIconClassName, searchIconClassNames, splitScreenHorizontallyIconClassName, splitScreenVerticallyIconClassName, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants'; +import { addIconClassName, disableTooltipIconClassName, enableTooltipIconClassName, openPropertiesIconClassNames, polygonBorderColor, polygonFillColor, resetZoomIconClassName, searchIconClassNames, splitScreenHorizontallyIconClassName, splitScreenVerticallyIconClassName, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { extname } from 'vs/base/common/path'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -49,6 +49,7 @@ export class ExecutionPlanComparisonEditorView { private _toggleOrientationAction: Action; private _searchNodeAction: Action; private _searchNodeActionForAddedPlan: Action; + private _toggleTooltipAction: Action; private _planComparisonContainer: HTMLElement; @@ -104,6 +105,7 @@ export class ExecutionPlanComparisonEditorView { private _bottomPlanRecommendations: ExecutionPlanViewHeader; private _bottomSimilarNode: Map = new Map(); private _latestRequestUuid: string; + private _areTooltipsEnabled: boolean = true; public get activeBottomPlanDiagram(): AzdataGraphView | undefined { if (this.bottomPlanDiagrams.length > 0) { @@ -153,6 +155,7 @@ export class ExecutionPlanComparisonEditorView { this._searchNodeAction = this._instantiationService.createInstance(SearchNodeAction, PlanIdentifier.Primary); this._searchNodeActionForAddedPlan = this._instantiationService.createInstance(SearchNodeAction, PlanIdentifier.Added); this._resetZoomAction = new ZoomReset(); + this._toggleTooltipAction = new ActionBarToggleTooltip(); const content: ITaskbarContent[] = [ { action: this._addExecutionPlanAction }, { action: this._zoomInAction }, @@ -162,7 +165,8 @@ export class ExecutionPlanComparisonEditorView { { action: this._toggleOrientationAction }, { action: this._propertiesAction }, { action: this._searchNodeAction }, - { action: this._searchNodeActionForAddedPlan } + { action: this._searchNodeActionForAddedPlan }, + { action: this._toggleTooltipAction } ]; this._taskbar.setContent(content); this.container.appendChild(this._taskbarContainer); @@ -407,6 +411,9 @@ export class ExecutionPlanComparisonEditorView { this._propertiesView.setSecondaryElement(executionPlanGraphs[0].root); this._addExecutionPlanAction.enabled = false; this._searchNodeActionForAddedPlan.enabled = true; + if (!this._areTooltipsEnabled) { + this.activeBottomPlanDiagram.toggleTooltip(); + } } this.refreshSplitView(); } @@ -481,6 +488,18 @@ export class ExecutionPlanComparisonEditorView { this._propertiesContainer.style.display = this._propertiesContainer.style.display === 'none' ? '' : 'none'; } + public toggleTooltips(): boolean { + let state: boolean; + if (this.activeTopPlanDiagram) { + state = this.activeTopPlanDiagram.toggleTooltip(); + } + if (this.activeBottomPlanDiagram) { + state = this.activeBottomPlanDiagram.toggleTooltip(); + } + this._areTooltipsEnabled = state; + return state; + } + public toggleOrientation(): void { if (this._orientation === 'vertical') { this._sashContainer.style.width = '100%'; @@ -680,6 +699,27 @@ class PropertiesAction extends Action { } } +export class ActionBarToggleTooltip extends Action { + public static ID = 'ep.tooltipToggleActionBar'; + public static WHEN_TOOLTIPS_ENABLED_LABEL = localize('executionPlanEnableTooltip', "Tooltips enabled"); + public static WHEN_TOOLTIPS_DISABLED_LABEL = localize('executionPlanDisableTooltip', "Tooltips disabled"); + + constructor() { + super(ActionBarToggleTooltip.ID, ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL, enableTooltipIconClassName); + } + + public override async run(context: ExecutionPlanComparisonEditorView): Promise { + const state = context.toggleTooltips(); + if (!state) { + this.class = disableTooltipIconClassName; + this.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_DISABLED_LABEL; + } else { + this.class = enableTooltipIconClassName; + this.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL; + } + } +} + enum PlanIdentifier { Primary = 0, Added = 1 diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts index ca42ae0521..9f0c403c3c 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts @@ -379,10 +379,14 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti diffIcon.title = notEqualTitle; break; case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.Number: - diffIcon = (parseFloat(v.primaryProp.displayValue) > parseFloat(v.secondaryProp.displayValue)) - ? { iconClass: Codicon.chevronRight.classNames, title: greaterThanTitle } - : { iconClass: Codicon.chevronLeft.classNames, title: lessThanTitle }; - + if (v.primaryProp.betterValue === sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyBetterValue.None) { + diffIcon.title = notEqualTitle; + diffIcon.iconClass = executionPlanComparisonPropertiesDifferent; + } else { + diffIcon = (parseFloat(v.primaryProp.displayValue) > parseFloat(v.secondaryProp.displayValue)) + ? { iconClass: Codicon.chevronRight.classNames, title: greaterThanTitle } + : { iconClass: Codicon.chevronLeft.classNames, title: lessThanTitle }; + } break; case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.String: diffIcon.iconClass = executionPlanComparisonPropertiesDifferent; diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts index 92950cfd2a..760caa4acb 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts @@ -10,7 +10,7 @@ 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 { propertiesSearchDescription, searchIconClassNames, searchPlaceholder, sortAlphabeticallyIconClassNames, sortByDisplayOrderIconClassNames, sortReverseAlphabeticallyIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants'; +import { filterIconClassNames, propertiesSearchDescription, searchPlaceholder, sortAlphabeticallyIconClassNames, sortByDisplayOrderIconClassNames, sortReverseAlphabeticallyIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants'; import { attachInputBoxStyler, attachTableStyler } from 'sql/platform/theme/common/styler'; import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/common/constants'; import { contrastBorder, inputBackground, listHoverBackground, listInactiveSelectionBackground } from 'vs/platform/theme/common/colorRegistry'; @@ -127,13 +127,13 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme ], { icon: true, label: false }); this._propertiesSearchInputContainer = DOM.$('.table-search'); - this._propertiesSearchInputContainer.classList.add('codicon', searchIconClassNames); + this._propertiesSearchInputContainer.classList.add('codicon', filterIconClassNames); this._propertiesSearchInput = this._register(new InputBox(this._propertiesSearchInputContainer, this._contextViewService, { ariaDescription: propertiesSearchDescription, placeholder: searchPlaceholder })); attachInputBoxStyler(this._propertiesSearchInput, this._themeService); - this._propertiesSearchInput.element.classList.add('codicon', searchIconClassNames); + this._propertiesSearchInput.element.classList.add('codicon', filterIconClassNames); this._searchAndActionBarContainer.appendChild(this._propertiesSearchInputContainer); this._register(this._propertiesSearchInput.onDidChange(e => { this.searchTable(e); @@ -331,7 +331,7 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme } else if (rawDataValue !== undefined) { dataValue = rawDataValue.text ?? rawDataValue.title; } - if (dataValue.toLowerCase().includes(search.toLowerCase())) { + if (dataValue?.toLowerCase().includes(search.toLowerCase())) { includeRow = true; break; } diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts index 34f09c2e50..e69daf2ea8 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts @@ -267,14 +267,11 @@ export class ExecutionPlanView implements ISashLayoutProvider { * use the scroll bars. */ diagramContainer.addEventListener('wheel', e => { - this._parent.scrollTop += e.deltaY; //Hiding all tooltips when we scroll. const element = document.getElementsByClassName('mxTooltip'); for (let i = 0; i < element.length; i++) { (element[i]).style.visibility = 'hidden'; } - e.preventDefault(); - e.stopPropagation(); }); this._planContainer.appendChild(diagramContainer); diff --git a/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/filter.svg b/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/filter.svg new file mode 100644 index 0000000000..dbfd04861a --- /dev/null +++ b/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/filterDark.svg b/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/filterDark.svg new file mode 100644 index 0000000000..0906e7c2bf --- /dev/null +++ b/src/sql/workbench/contrib/executionPlan/browser/images/actionIcons/filterDark.svg @@ -0,0 +1 @@ + diff --git a/src/sql/workbench/contrib/executionPlan/browser/media/executionPlan.css b/src/sql/workbench/contrib/executionPlan/browser/media/executionPlan.css index 6e65ecef0e..1190ff95f8 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/media/executionPlan.css +++ b/src/sql/workbench/contrib/executionPlan/browser/media/executionPlan.css @@ -390,6 +390,24 @@ However we always want it to be the width of the container it is resizing. background-repeat: no-repeat; } +.eps-container .ep-filter-icon, +.top-operations-tab .ep-filter-icon { + background-image: url(../images/actionIcons/filter.svg); + background-size: 16px 16px; + background-position: center; + background-repeat: no-repeat; +} + +.vs-dark .eps-container .ep-filter-icon, +.hc-black .eps-container .ep-filter-icon, +.vs-dark .top-operations-tab .ep-filter-icon, +.hc-black .top-operations-tab .ep-filter-icon { + background-image: url(../images/actionIcons/filterDark.svg); + background-size: 16px 16px; + background-position: center; + background-repeat: no-repeat; +} + .eps-container .ep-sort-alphabetically-icon { background-image: url(../images/actionIcons/sortAlphabetically.svg); background-size: 16px 16px; diff --git a/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts b/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts index 106e146a1e..c5dc406800 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts @@ -29,7 +29,7 @@ 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'; +import { filterIconClassNames, searchPlaceholder, topOperationsSearchDescription } from 'sql/workbench/contrib/executionPlan/browser/constants'; const TABLE_SORT_COLUMN_KEY = 'tableCostColumnForSorting'; @@ -163,14 +163,14 @@ export class TopOperationsTabView extends Disposable implements IPanelView { const headerSearchBarContainer = DOM.$('.top-operations-header-search-bar'); headerContainer.appendChild(headerSearchBarContainer); - headerContainer.classList.add('codicon', searchIconClassNames); + headerContainer.classList.add('codicon', filterIconClassNames); const topOperationsSearchInput = new InputBox(headerSearchBarContainer, this._contextViewService, { ariaDescription: topOperationsSearchDescription, placeholder: searchPlaceholder }); attachInputBoxStyler(topOperationsSearchInput, this._themeService); - topOperationsSearchInput.element.classList.add('codicon', searchIconClassNames); + topOperationsSearchInput.element.classList.add('codicon', filterIconClassNames); const header = this._instantiationService.createInstance(ExecutionPlanViewHeader, headerInfoContainer, { planIndex: index,