Skip to content

[ES|QL Controls] Highlight related panels on click, refactor Related Panels system#264426

Open
Zacqary wants to merge 55 commits into
elastic:mainfrom
Zacqary:204508-esql-highlight
Open

[ES|QL Controls] Highlight related panels on click, refactor Related Panels system#264426
Zacqary wants to merge 55 commits into
elastic:mainfrom
Zacqary:204508-esql-highlight

Conversation

@Zacqary
Copy link
Copy Markdown
Contributor

@Zacqary Zacqary commented Apr 20, 2026

Summary

Closes #204508

Refactor

Removes the Related Panels Manager from the dashboard API and replaces this with a relatedPanels$ publishing subject on all panels. All panels now compute which of their siblings they're related to.

  • Filter controls are related to all panels in their same scope (unsectioned, i.e. global, or in the same section)
  • ES|QL controls have the same logic, except they also check to see if panels in their scope consume their variable
  • All panels are related to all filter controls in the same scope
  • ES|QL visualizations are related to ES|QL controls in the same scope that publish a variable they consume

Enhancement

Allows the user to click on ES|QL control labels to highlight all their related panels. Also fixes a bug where giving an ES|QL control a title did not save when saving the dashboard.
Screenshot 2026-04-23 at 10 46 40 PM
Screenshot 2026-04-23 at 10 45 21 PM

And displays a warning on ES|QL controls with no related panels:
Screenshot 2026-04-23 at 10 40 08 PM

Chained ES|QL controls (a control that references the variable of another control) are also supported:
Screenshot 2026-04-23 at 10 45 55 PM

Implementation notes

  • Code is calling this "indicate" related panels because we use "highlight" for the animation that plays when you create a new panel. At this rate, Kibana is projected to run out of verbs by mid-2027 and we will have to invent new words like "gleeglorp" whenever we add a new feature.
  • You can only indicate related panels in Edit mode, not View Mode
  • You can only select one control at a time
  • Dashboards are now CanIndicateRelatedChildren, only ES|QL controls currently publish CanIndicateRelatedSiblings so as not to enable this behavior on filter controls
  • Selecting a control will save this state in the Dashboard Backup service to preserve it on page refresh, but won't save it in the dashboard schema when the user saves the dashboard. It's convenient to preserve the page state for the user's sake but probably not necessary for saving and sharing.

Testing

  1. Add the Kibana Logs Sample Data
  2. Save the following dashboard to an ndjson file and import it as a SavedObject
Click to expand
{"accessControl":{"accessMode":"default","owner":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"},"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"}}"},"optionsJSON":"{\"hidePanelTitles\":false,\"hidePanelBorders\":false,\"useMargins\":true,\"autoApplyFilters\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false}","panelsJSON":"[{\"type\":\"esql_control\",\"embeddableConfig\":{\"selected_options\":[\"120.56.165.242\"],\"variable_name\":\"ip\",\"single_select\":true,\"variable_type\":\"values\",\"control_type\":\"VALUES_FROM_QUERY\",\"esql_query\":\"FROM kibana_sample_data_logs | WHERE geo.dest == ?geo_dest | STATS BY ip\",\"title\":\"IP (chained to geo.dest)\"},\"panelIndex\":\"6108de48-1993-4051-8b51-0cd6dca622c5\",\"gridData\":{\"y\":0,\"x\":0,\"w\":12,\"h\":2,\"i\":\"6108de48-1993-4051-8b51-0cd6dca622c5\"}},{\"type\":\"vis\",\"embeddableConfig\":{\"title\":\"\",\"attributes\":{\"title\":\"Metric\",\"references\":[],\"state\":{\"datasourceStates\":{\"textBased\":{\"layers\":{\"64a4cc71-c831-4644-9022-3fc833b8be77\":{\"index\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"query\":{\"esql\":\"FROM kibana_sample_data_logs | STATS COUNT(*) | RENAME `COUNT(*)` as `Visits`\"},\"columns\":[{\"columnId\":\"Visits\",\"fieldName\":\"Visits\",\"meta\":{\"type\":\"number\",\"esType\":\"long\"},\"inMetricDimension\":true}],\"timeField\":\"@timestamp\"}},\"indexPatternRefs\":[{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeField\":\"@timestamp\"}]}},\"filters\":[],\"query\":{\"esql\":\"FROM kibana_sample_data_logs | STATS COUNT(*) | RENAME `COUNT(*)` as `Visits`\"},\"visualization\":{\"layerId\":\"64a4cc71-c831-4644-9022-3fc833b8be77\",\"layerType\":\"data\",\"metricAccessor\":\"Visits\"},\"adHocDataViews\":{\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\":{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeFieldName\":\"@timestamp\",\"sourceFilters\":[],\"type\":\"esql\",\"fieldFormats\":{},\"runtimeFieldMap\":{},\"allowNoIndex\":false,\"name\":\"kibana_sample_data_logs\",\"allowHidden\":false}}},\"visualizationType\":\"lnsMetric\",\"version\":2}},\"panelIndex\":\"fee866b2-4651-43a4-88a0-744aa44895b9\",\"gridData\":{\"y\":2,\"x\":0,\"w\":12,\"h\":7,\"i\":\"fee866b2-4651-43a4-88a0-744aa44895b9\"}},{\"type\":\"vis\",\"embeddableConfig\":{\"title\":\"\",\"attributes\":{\"title\":\"\",\"references\":[],\"state\":{\"datasourceStates\":{\"textBased\":{\"layers\":{\"44a67c01-79aa-4dec-b3c8-b4be0e4bdfc9\":{\"index\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE geo.dest == ?geo_dest | STATS COUNT(*) | RENAME `COUNT(*)` as `Visits by geo.dest`\"},\"columns\":[{\"columnId\":\"Visits by geo.dest\",\"fieldName\":\"Visits by geo.dest\",\"label\":\"Visits by geo.dest\",\"customLabel\":false,\"meta\":{\"type\":\"number\",\"esType\":\"long\"},\"inMetricDimension\":true}],\"timeField\":\"@timestamp\"}},\"indexPatternRefs\":[{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeField\":\"@timestamp\"}]}},\"filters\":[],\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE geo.dest == ?geo_dest | STATS COUNT(*) | RENAME `COUNT(*)` as `Visits by geo.dest`\"},\"visualization\":{\"layerId\":\"44a67c01-79aa-4dec-b3c8-b4be0e4bdfc9\",\"layerType\":\"data\",\"metricAccessor\":\"Visits by geo.dest\"},\"adHocDataViews\":{\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\":{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeFieldName\":\"@timestamp\",\"sourceFilters\":[],\"type\":\"esql\",\"fieldFormats\":{},\"runtimeFieldMap\":{},\"allowNoIndex\":false,\"name\":\"kibana_sample_data_logs\",\"allowHidden\":false,\"managed\":false}},\"needsRefresh\":false},\"visualizationType\":\"lnsMetric\",\"version\":2}},\"panelIndex\":\"8d18ca49-2472-43f3-b1ce-0395e461e309\",\"gridData\":{\"y\":0,\"x\":12,\"w\":12,\"h\":7,\"i\":\"8d18ca49-2472-43f3-b1ce-0395e461e309\"}},{\"type\":\"esql_control\",\"embeddableConfig\":{\"selected_options\":[\"css\"],\"variable_name\":\"extension\",\"single_select\":true,\"variable_type\":\"values\",\"control_type\":\"VALUES_FROM_QUERY\",\"esql_query\":\"FROM kibana_sample_data_logs | WHERE geo.dest == ?geo_dest | STATS BY extension\",\"title\":\"Extension\"},\"panelIndex\":\"88e08770-8623-4b3a-a574-793505c6cf20\",\"gridData\":{\"y\":0,\"x\":24,\"w\":12,\"h\":2,\"i\":\"88e08770-8623-4b3a-a574-793505c6cf20\"}},{\"type\":\"vis\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"references\":[],\"state\":{\"datasourceStates\":{\"textBased\":{\"layers\":{\"bb058e6a-aaeb-4740-89de-6ba02134a152\":{\"index\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE extension == ?extension | STATS COUNT(*) | RENAME `COUNT(*)` as `Visits by extension`\"},\"columns\":[{\"columnId\":\"Visits by extension\",\"fieldName\":\"Visits by extension\",\"label\":\"Visits by extension\",\"customLabel\":false,\"meta\":{\"type\":\"number\",\"esType\":\"long\"},\"inMetricDimension\":true}],\"timeField\":\"@timestamp\"}},\"indexPatternRefs\":[{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeField\":\"@timestamp\"}]}},\"filters\":[],\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE extension == ?extension | STATS COUNT(*) | RENAME `COUNT(*)` as `Visits by extension`\"},\"visualization\":{\"layerId\":\"bb058e6a-aaeb-4740-89de-6ba02134a152\",\"layerType\":\"data\",\"metricAccessor\":\"Visits by extension\"},\"adHocDataViews\":{\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\":{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeFieldName\":\"@timestamp\",\"sourceFilters\":[],\"type\":\"esql\",\"fieldFormats\":{},\"runtimeFieldMap\":{},\"allowNoIndex\":false,\"name\":\"kibana_sample_data_logs\",\"allowHidden\":false,\"managed\":false}},\"needsRefresh\":false},\"visualizationType\":\"lnsMetric\",\"version\":2},\"title\":\"\",\"drilldowns\":[]},\"panelIndex\":\"2174ae82-3535-44d9-8c69-2b5aaf280d18\",\"gridData\":{\"y\":2,\"x\":24,\"w\":12,\"h\":7,\"i\":\"2174ae82-3535-44d9-8c69-2b5aaf280d18\"}},{\"type\":\"vis\",\"embeddableConfig\":{\"title\":\"\",\"attributes\":{\"title\":\"Metric\",\"references\":[],\"state\":{\"datasourceStates\":{\"textBased\":{\"layers\":{\"323d8ae0-0a4e-4540-84e2-0d36c8f15b46\":{\"index\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"query\":{\"esql\":\"FROM kibana_sample_data_logs | STATS COUNT_DISTINCT(clientip) | RENAME `COUNT_DISTINCT(clientip)` as `Unique Visitors`\"},\"columns\":[{\"columnId\":\"Unique Visitors\",\"fieldName\":\"Unique Visitors\",\"meta\":{\"type\":\"number\",\"esType\":\"long\"},\"inMetricDimension\":true}],\"timeField\":\"@timestamp\"}},\"indexPatternRefs\":[{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeField\":\"@timestamp\"}]}},\"filters\":[],\"query\":{\"esql\":\"FROM kibana_sample_data_logs | STATS COUNT_DISTINCT(clientip) | RENAME `COUNT_DISTINCT(clientip)` as `Unique Visitors`\"},\"visualization\":{\"layerId\":\"323d8ae0-0a4e-4540-84e2-0d36c8f15b46\",\"layerType\":\"data\",\"metricAccessor\":\"Unique Visitors\",\"palette\":{\"name\":\"custom\",\"type\":\"palette\",\"params\":{\"steps\":3,\"name\":\"custom\",\"reverse\":false,\"rangeType\":\"number\",\"rangeMin\":0,\"rangeMax\":null,\"progression\":\"fixed\",\"stops\":[{\"color\":\"#D23115\",\"stop\":500},{\"color\":\"#FCC400\",\"stop\":1000},{\"color\":\"#68BC00\",\"stop\":1640}],\"colorStops\":[{\"color\":\"#D23115\",\"stop\":0},{\"color\":\"#FCC400\",\"stop\":500},{\"color\":\"#68BC00\",\"stop\":1000}],\"continuity\":\"above\",\"maxSteps\":5}}},\"adHocDataViews\":{\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\":{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeFieldName\":\"@timestamp\",\"sourceFilters\":[],\"type\":\"esql\",\"fieldFormats\":{},\"runtimeFieldMap\":{},\"allowNoIndex\":false,\"name\":\"kibana_sample_data_logs\",\"allowHidden\":false}}},\"visualizationType\":\"lnsMetric\",\"version\":2}},\"panelIndex\":\"6a8785a6-f4cd-4fc7-a9d3-b66ddb07db5d\",\"gridData\":{\"y\":9,\"x\":0,\"w\":12,\"h\":7,\"i\":\"6a8785a6-f4cd-4fc7-a9d3-b66ddb07db5d\"}},{\"type\":\"vis\",\"embeddableConfig\":{\"title\":\"\",\"attributes\":{\"title\":\"\",\"references\":[],\"state\":{\"datasourceStates\":{\"textBased\":{\"layers\":{\"a37be685-3f6e-4b97-b7dd-37b4e5548c5e\":{\"index\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE geo.dest == ?geo_dest | STATS COUNT_DISTINCT(clientip) | RENAME `COUNT_DISTINCT(clientip)` as `Unique Visitors by geo.dest`\"},\"columns\":[{\"columnId\":\"Unique Visitors by geo.dest\",\"fieldName\":\"Unique Visitors by geo.dest\",\"label\":\"Unique Visitors by geo.dest\",\"customLabel\":false,\"meta\":{\"type\":\"number\",\"esType\":\"long\"},\"inMetricDimension\":true}],\"timeField\":\"@timestamp\"}},\"indexPatternRefs\":[{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeField\":\"@timestamp\"}]}},\"filters\":[],\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE geo.dest == ?geo_dest | STATS COUNT_DISTINCT(clientip) | RENAME `COUNT_DISTINCT(clientip)` as `Unique Visitors by geo.dest`\"},\"visualization\":{\"layerId\":\"a37be685-3f6e-4b97-b7dd-37b4e5548c5e\",\"layerType\":\"data\",\"metricAccessor\":\"Unique Visitors by geo.dest\",\"palette\":{\"name\":\"custom\",\"type\":\"palette\",\"params\":{\"steps\":3,\"name\":\"custom\",\"reverse\":false,\"rangeType\":\"number\",\"rangeMin\":0,\"rangeMax\":null,\"progression\":\"fixed\",\"stops\":[{\"color\":\"#D23115\",\"stop\":500},{\"color\":\"#FCC400\",\"stop\":1000},{\"color\":\"#68BC00\",\"stop\":1640}],\"colorStops\":[{\"color\":\"#D23115\",\"stop\":0},{\"color\":\"#FCC400\",\"stop\":500},{\"color\":\"#68BC00\",\"stop\":1000}],\"continuity\":\"above\",\"maxSteps\":5}}},\"adHocDataViews\":{\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\":{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeFieldName\":\"@timestamp\",\"sourceFilters\":[],\"type\":\"esql\",\"fieldFormats\":{},\"runtimeFieldMap\":{},\"allowNoIndex\":false,\"name\":\"kibana_sample_data_logs\",\"allowHidden\":false,\"managed\":false}},\"needsRefresh\":false},\"visualizationType\":\"lnsMetric\",\"version\":2}},\"panelIndex\":\"33c701cd-9e46-4f30-8697-02be563a27c9\",\"gridData\":{\"y\":7,\"x\":12,\"w\":12,\"h\":7,\"i\":\"33c701cd-9e46-4f30-8697-02be563a27c9\"}},{\"type\":\"vis\",\"embeddableConfig\":{\"title\":\"\",\"attributes\":{\"title\":\"\",\"references\":[],\"state\":{\"datasourceStates\":{\"textBased\":{\"layers\":{\"6e9ab6e8-d953-4fb0-a056-39018adb3338\":{\"index\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE extension == ?extension | STATS COUNT_DISTINCT(clientip) | RENAME `COUNT_DISTINCT(clientip)` as `Unique Visitors by extension`\"},\"columns\":[{\"columnId\":\"Unique Visitors by extension\",\"fieldName\":\"Unique Visitors by extension\",\"label\":\"Unique Visitors by extension\",\"customLabel\":false,\"meta\":{\"type\":\"number\",\"esType\":\"long\"},\"inMetricDimension\":true}],\"timeField\":\"@timestamp\"}},\"indexPatternRefs\":[{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeField\":\"@timestamp\"}]}},\"filters\":[],\"query\":{\"esql\":\"FROM kibana_sample_data_logs | WHERE extension == ?extension | STATS COUNT_DISTINCT(clientip) | RENAME `COUNT_DISTINCT(clientip)` as `Unique Visitors by extension`\"},\"visualization\":{\"layerId\":\"6e9ab6e8-d953-4fb0-a056-39018adb3338\",\"layerType\":\"data\",\"metricAccessor\":\"Unique Visitors by extension\",\"palette\":{\"name\":\"custom\",\"type\":\"palette\",\"params\":{\"steps\":3,\"name\":\"custom\",\"reverse\":false,\"rangeType\":\"number\",\"rangeMin\":0,\"rangeMax\":null,\"progression\":\"fixed\",\"stops\":[{\"color\":\"#D23115\",\"stop\":500},{\"color\":\"#FCC400\",\"stop\":1000},{\"color\":\"#68BC00\",\"stop\":1640}],\"colorStops\":[{\"color\":\"#D23115\",\"stop\":0},{\"color\":\"#FCC400\",\"stop\":500},{\"color\":\"#68BC00\",\"stop\":1000}],\"continuity\":\"above\",\"maxSteps\":5}}},\"adHocDataViews\":{\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\":{\"id\":\"e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a\",\"title\":\"kibana_sample_data_logs\",\"timeFieldName\":\"@timestamp\",\"sourceFilters\":[],\"type\":\"esql\",\"fieldFormats\":{},\"runtimeFieldMap\":{},\"allowNoIndex\":false,\"name\":\"kibana_sample_data_logs\",\"allowHidden\":false,\"managed\":false}},\"needsRefresh\":false},\"visualizationType\":\"lnsMetric\",\"version\":2}},\"panelIndex\":\"0436b504-23f2-43fb-b985-cbc59a78db20\",\"gridData\":{\"y\":9,\"x\":24,\"w\":12,\"h\":7,\"i\":\"0436b504-23f2-43fb-b985-cbc59a78db20\"}}]","pinned_panels":{"panels":{"406812b2-db25-4cb1-be57-ff38eccab340":{"config":{"control_type":"VALUES_FROM_QUERY","esql_query":"FROM kibana_sample_data_logs | STATS BY geo.dest","selected_options":["AL"],"single_select":true,"title":"Geo Dest","variable_name":"geo_dest","variable_type":"values"},"grow":false,"order":0,"type":"esql_control","width":"medium"}}},"timeRestore":false,"title":"ES|QL Test"},"coreMigrationVersion":"8.8.0","created_at":"2026-04-23T18:12:33.957Z","created_by":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0","id":"4ed63a99-f7e5-40bd-af9a-bd3971ec6bc4","managed":false,"references":[],"type":"dashboard","typeMigrationVersion":"10.3.0","updated_at":"2026-04-23T20:37:46.876Z","updated_by":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0","version":"WzIyLDFd"}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}

Checklist

@Zacqary Zacqary force-pushed the 204508-esql-highlight branch from b7cc28a to 087ca94 Compare April 20, 2026 14:11
@Zacqary Zacqary added release_note:enhancement Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t// loe:medium Medium Level of Effort backport:version Backport to applied version labels v9.5.0 impact:medium Addressing this issue will have a medium level of impact on the quality/strength of our product. Feature:Dashboard Dashboard related features labels Apr 20, 2026
Comment thread src/platform/packages/shared/controls/controls-schemas/src/types.ts Outdated
@Zacqary Zacqary force-pushed the 204508-esql-highlight branch from 087ca94 to 4021c2d Compare April 20, 2026 14:36
@Zacqary Zacqary force-pushed the 204508-esql-highlight branch from 4021c2d to d3b43ad Compare April 20, 2026 14:42
@Zacqary Zacqary force-pushed the 204508-esql-highlight branch 2 times, most recently from 8b4343b to ea0fda0 Compare April 20, 2026 15:01
@Zacqary Zacqary force-pushed the 204508-esql-highlight branch 9 times, most recently from 5270bb4 to 5aee652 Compare April 21, 2026 10:04
@Zacqary Zacqary marked this pull request as ready for review April 21, 2026 10:08
@Zacqary Zacqary requested review from a team as code owners April 21, 2026 10:09
@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/kibana-presentation (Team:Presentation)

Copy link
Copy Markdown
Contributor

@andrimal andrimal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vis changes LGTM! (only one mock file updated)

@andreadelrio
Copy link
Copy Markdown
Contributor

@florent-leborgne could we get your input of the copy for the case where a ES|QL variable is not being used by any (ES|QL) visualization?

image
  • I originally had: This control isn't used in any ES|QL visualization. Update your visualizations to use it.
  • In this PR, @Zacqary has: This variable isn't used in any visualization.

Copy link
Copy Markdown
Member

@florent-leborgne florent-leborgne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick copy check based on @andreadelrio's request

Copy link
Copy Markdown
Contributor

@andreadelrio andreadelrio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design changes LGTM

Zacqary and others added 3 commits May 20, 2026 10:08
…ents/control_label_tooltip.tsx

Co-authored-by: Florent LB <florent.leborgne@elastic.co>
Copy link
Copy Markdown
Member

@florent-leborgne florent-leborgne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy LGTM

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to blur unrelated panels when selecting a control? If so - maybe just the blur on unrelated panels is enough without the border around related panels? Then the teal border can apply just to the selected control. This feels more consistent with the editing experience IMO cc @andreadelrio

Screen.Recording.2026-05-22.at.1.55.38.PM.mov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Heenawter I see what you mean and yes, I think this would be a valid way to simplify the styles. The blur of unrelated panels is probably enough.


export const useIndicateRelatedPanelsSelector = (
api: unknown,
skipDebounce?: boolean // For faster testing
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this relevant anymore?

Comment on lines +23 to +24
setIndicateRelatedPanelsId: (panelId?: string) => void;
indicateRelatedPanelsId$: BehaviorSubject<string | undefined>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
setIndicateRelatedPanelsId: (panelId?: string) => void;
indicateRelatedPanelsId$: BehaviorSubject<string | undefined>;
setRelatedPanelIds: (panelId?: string) => void;
relatedPanelIds$: BehaviorSubject<string | undefined>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These refer to the ID of the panel that's been selected to do the indicating, not a list of panel IDs that are related. I'll see if I can come up with a clearer name.

export interface PresentationContainer<ApiType extends unknown = unknown> extends CanAddNewPanel {
export interface PresentationContainer<ApiType extends unknown = unknown>
extends CanAddNewPanel,
Partial<CanIndicateRelatedChildren> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced that this should be part of PresentationContainer rather than just the Dashboard API.... Is there a reason you put it here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering that Discover wants to use related panels at some point. We've decided that's not a strong design goal anymore, but I think this is a low-impact way to make that easier.

Copy link
Copy Markdown
Contributor

@Heenawter Heenawter May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally still think it would be cleaner and easier to understand if the Dashboard API implemented this rather than PresentationContainer. After all, PresentationContainer doesn't have all of the highlightedPanels$ stuff - this is just on Dashboard - and they seem closely related to me, especially now that a bulk of the logic for related panels is in track_panel :)

Comment on lines +65 to +75
const tooltipLabel$ = new BehaviorSubject<string>(state.title ?? '');
const tooltipLabelSubscription = combineLatest([
selections.api.esqlVariable$,
labelManager.api.label$,
])
.pipe(
map(([{ key: variableName, type: variableType }, label]) => {
return getTooltipTitle(variableName, variableType, label);
})
)
.subscribe((next) => tooltipLabel$.next(next));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like overkill to me. Can we not just calculate this in the ControlLabelTooltip component? It doesn't need to change on-the-fly, so it can be recomputed each time the tooltip renders; maybe we could even just do something simple like a getter in the API rather than a subject that we need to subscribe to?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll test it again to make sure I didn't miss something but I believe EUI tooltips don't actually re-render the way we expect them to, which is why we need to explicitly pipe a new tooltip into them on change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it's been a while since I did this and forgot the context. This isn't to deal with a rendering issue, it's to keep ControlLabelTooltip generic and letting the ES|QL control handle formatting ES|QL variables names.

Note what getTooltipTitle does:

export const getTooltipTitle = (
  variableName: string,
  variableType: ESQLVariableType,
  label?: string
) => {
  const variableWithPrefix = `${getVariableNamePrefix(variableType)}${variableName}`;

  return label && label !== variableName ? `${label} (${variableWithPrefix})` : variableWithPrefix;
};

It's formatting the tooltip title as Control Name (?variable_name). This is an ES|QL control concern only, and we want to keep ControlLabelTooltip free to theoretically handle tooltips for other control types in the future.

BehaviorSubjects are the idiomatic way that controls output their titles and labels even though they only change on edit, so that's what I went with here.

Comment on lines +44 to +55
if (!parentApiSubscription.current) {
const sub = combineLatest([
parentApiLoaded.viewMode$,
parentApiLoaded.indicateRelatedPanelsId$,
api.relatedPanels$,
]).subscribe(([vm, indicateId, relatedPanelIds]) => {
setViewMode(vm);
setIndicateRelatedPanelsId(indicateId);
setRelatedPanels(relatedPanelIds);
});
parentApiSubscription.current = sub;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use useBatchedPublishingSubjects?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me give that a try. It didn't work before the refactor that eliminated the related panels manager but with the reduced complexity we might be able to do that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok yeah it's the parentApiLoaded issue. ControlPanel is guaranteed to have parentApi available. ConditionalLabelWrapper is not, and needs to derive parentApi from its passed api. But in my testing, parentApi doesn't immediately get attached to api when an unpinned panel renders.

I think this is counter-intuitively the simplest way to handle it.

@kibanamachine
Copy link
Copy Markdown
Contributor

kibanamachine commented May 29, 2026

💔 Build Failed

Failed CI Steps

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
agentBuilder 1963 1969 +6
agentBuilderDashboards 597 603 +6
aiops 620 624 +4
alertingVTwo 1150 1156 +6
apm 2256 2266 +10
canvas 1317 1321 +4
cases 2072 2078 +6
cloudSecurityPosture 824 830 +6
console 389 395 +6
controls 433 444 +11
dashboard 1093 1102 +9
dashboardMarkdown 129 133 +4
data 641 647 +6
datasetQuality 1121 1127 +6
dataVisualizer 933 939 +6
discover 2106 2116 +10
discoverEnhanced 111 115 +4
embeddable 267 271 +4
embeddableAlertsTable 474 478 +4
esql 1068 1074 +6
esqlDataGrid 493 499 +6
eventAnnotationListing 758 764 +6
fleet 2218 2224 +6
imageEmbeddable 169 173 +4
infra 1913 1923 +10
inputControlVis 135 139 +4
lens 1811 1817 +6
links 156 160 +4
lists 473 479 +6
logsShared 485 491 +6
maps 1382 1392 +10
ml 4348 4354 +6
observability 1817 1827 +10
observabilityAIAssistantApp 853 859 +6
observabilityLogsExplorer 306 312 +6
osquery 816 822 +6
queryActivity 310 316 +6
reporting 261 265 +4
searchPlayground 635 641 +6
securitySolution 9577 9587 +10
slo 1387 1397 +10
stackAlerts 236 242 +6
streamsApp 1955 1961 +6
synthetics 1311 1315 +4
transform 951 957 +6
triggersActionsUi 1863 1873 +10
unifiedDocViewer 982 988 +6
urlDrilldown 158 162 +4
visTypeTimeseries 528 532 +4
visTypeVega 1837 1843 +6
visualizations 777 783 +6
workflowsManagement 1860 1866 +6
total +326

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
apm 2.8MB 2.8MB +4.0KB
controls 528.0KB 536.6KB +8.6KB
dashboard 1.1MB 1.1MB +3.2KB
discover 1.9MB 1.9MB +4.0KB
esql 920.7KB 920.7KB +10.0B
infra 1.5MB 1.5MB +3.8KB
maps 3.2MB 3.2MB +3.8KB
observability 2.1MB 2.1MB +3.8KB
securitySolution 12.1MB 12.1MB +3.8KB
slo 1.2MB 1.2MB +3.9KB
triggersActionsUi 2.6MB 2.6MB +3.8KB
total +42.9KB

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
controls 9.2KB 9.2KB -1.0B
discover 28.9KB 28.9KB +1.0B
maps 41.1KB 41.1KB -4.0B
slo 37.0KB 37.0KB -4.0B
total -8.0B
Unknown metric groups

ESLint disabled line counts

id before after diff
controls 4 5 +1

Total ESLint disabled count

id before after diff
controls 4 5 +1

History

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:version Backport to applied version labels Feature:Dashboard Dashboard related features Feature:Drilldowns Embeddable panel Drilldowns Feature:Embedding Embedding content via iFrame impact:medium Addressing this issue will have a medium level of impact on the quality/strength of our product. loe:medium Medium Level of Effort release_note:enhancement reviewer:skip-ai Skip automatic AI reviews Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t// v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ES|QL Controls] Highlight panels

10 participants