Skip to content

Conversation

@markov00
Copy link
Contributor

@markov00 markov00 commented Dec 1, 2025

Summary

This PR enables the possibility to configure ESQL charts with a multi-field breakdown.

The code changes are the following:

  • I've renamed and retyped the splitAccessor?: string visualization state parameter of XY charts into splitAccesors?: string[]. This allows adding multiple separate column/dimension into that parameter, that was already available at the expression level to describe a breakdown formed by multiple data columns.
  • A runtime migration was added before the color mapping runtime migration to support correctly existing charts
  • The color mapping was one of the main problem in the refactoring due to the need of specifying more than one column to describe a color. This was done by reusing the same structure of the multi-terms class used for multi-terms aggregation. This class has already everything in place to contain 2 different values and also 2 different formatting options.
  • The changes are applied only for ESQL, maintaining unchanged the existing behaviour with DSL. (even if enabling multi-fields would work too in DSL world, for this PR I tried to limit the logic to just the ESQL part.
  • During one review it was discovered that multi-fields title aggregation where contatenated in a different way than in the color mapping/multi-term. I changed the concatenation to use the same > simbol.
  • A strange behaviour was discovered when testing the breakdown dimension reordering: in the Visualization state the logic was a bit off and caused new duplicated dimensions to popup: I've added a specific method that support reordering the dimension in the visualization state to correctly fix that issue.
Screenshot 2025-12-17 at 09 56 24

Fix #235743

Part of #236682
What is currently missing, and will be added in a subsequent PR:

  • A way to warn the user when not every field returned by the query is used in the chart configuration.

Copy link
Contributor

@stratoula stratoula left a comment

Choose a reason for hiding this comment

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

Marco I tested it a lot, especially the suggestions logic and I don't see any regressions. It actually works really well!

Copy link
Contributor

Choose a reason for hiding this comment

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

Even the filtering works!

image

@markov00 markov00 marked this pull request as ready for review December 17, 2025 08:58
@markov00 markov00 requested review from a team as code owners December 17, 2025 08:58
@markov00 markov00 requested a review from CAWilson94 December 17, 2025 08:58
@markov00 markov00 added release_note:enhancement Team:Visualizations Team label for Lens, elastic-charts, Graph, legacy editors (TSVB, Visualize, Timelion) t// Feature:Lens backport:skip This PR does not require backporting labels Dec 17, 2025
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-visualizations (Team:Visualizations)

@elasticmachine
Copy link
Contributor

Pinging @elastic/obs-ux-management-team (Team:obs-ux-management)

Copy link
Contributor

@jughosta jughosta left a comment

Choose a reason for hiding this comment

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

Data Discovery changes LGTM

@elasticmachine
Copy link
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #82 / cases security and spaces enabled: trial Common analytics indexes backfill task should backfill the cases attachments index

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
lens 1662 1665 +3

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
@kbn/lens-common 1347 1349 +2
lens 600 602 +2
total +4

Async chunks

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

id before after diff
dashboard 721.4KB 721.4KB +21.0B
datasetQuality 588.3KB 588.3KB +6.0B
discover 1.3MB 1.3MB +85.0B
eventAnnotationListing 206.8KB 206.8KB +18.0B
exploratoryView 138.8KB 138.8KB +3.0B
expressionPartitionVis 37.5KB 37.5KB -78.0B
expressionTagcloud 17.2KB 17.2KB +11.0B
expressionXY 98.5KB 98.9KB +414.0B
infra 1.1MB 1.1MB +21.0B
lens 1.9MB 1.9MB +3.6KB
observability 1.7MB 1.7MB +21.0B
observabilityAIAssistantApp 267.9KB 267.9KB +21.0B
onechat 519.8KB 519.9KB +21.0B
securitySolution 10.8MB 10.8MB +21.0B
unifiedDocViewer 370.6KB 370.6KB +21.0B
visTypeTimeseries 440.7KB 440.8KB +14.0B
visTypeXy 43.7KB 43.7KB +12.0B
total +4.2KB

Page load bundle

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

id before after diff
expressionPartitionVis 26.7KB 27.0KB +275.0B
expressionTagcloud 11.8KB 12.1KB +275.0B
expressionXY 41.5KB 41.8KB +277.0B
lens 64.5KB 64.6KB +39.0B
total +866.0B
Unknown metric groups

API count

id before after diff
@kbn/lens-common 1491 1493 +2
lens 703 705 +2
total +4

History

// remove categories one maching an assignment
return !assignmentMatcher.hasMatch(category);
})
// setting the Map keys as the strinified version of the rawValue
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typo stringified

}
return `${splitValues.join(' - ')}${yAccessorTitle ? ' - ' + yAccessorTitle : ''}`;
return `${splitValues.join(MULTI_FIELD_KEY_SEPARATOR)}${
yAccessorTitle ? ' - ' + yAccessorTitle : ''
Copy link
Contributor

Choose a reason for hiding this comment

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

should this ' - ' also be MULTI_FIELD_KEY_SEPARATOR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No that's a different separator, this separates the breakdown categories (called splitValues and concatenated with the from the MULTI_FIELD_KEY_SEPARATOR) from the metric name

@kpatticha kpatticha self-requested a review December 19, 2025 14:37
Copy link
Contributor

@baileycash-elastic baileycash-elastic left a comment

Choose a reason for hiding this comment

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

actionable-obs changes lgtm

Copy link
Contributor

@PhilippeOberti PhilippeOberti left a comment

Choose a reason for hiding this comment

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

Code review only, LGTM for the @elastic/security-threat-hunting-investigations team

layers: Array<DeprecatedSplitAccessorLayer | XYLayerConfig>;
}

export function convertToSplitAccessorsFn(state: DeprecatedSplitAccessorState | XYState): XYState {
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like this is an exact duplicate of the one in split_accessor.ts - should we move to a shared utility?

legacyMode: boolean = false // stringifies raw values
): SerializedValue[] {
if (!accessor) return [];
if (!accessors) return [];
Copy link
Contributor

Choose a reason for hiding this comment

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

With the default being now an array, should we consider it like this: (!accessors || accessors.length === 0) return [];


const splits =
!column || !data.tables[layer.layerId]
!columns || !data.tables[layer.layerId]
Copy link
Contributor

Choose a reason for hiding this comment

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

We could add a check for array length here too: !columns || columns.length === 0 | !data.tables[layer.layerId]

Copy link
Contributor

@walterra walterra left a comment

Choose a reason for hiding this comment

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

Did some local testing, LGTM! Tested:

  • Used sample logs/flights
  • In query mode, was able to add additional breakdown fields
  • In form mode, only a single breakdown field can be used
  • Reordering breakdown fields works
  • ES|QL with multiple BYs will add multiple fields to breakdowns

@kpatticha
Copy link
Contributor

Hi @markov00

I'm mainly testing the UI in Discover, and it works really nicely overall.

I do have a few questions, though.

In the code, I don't see any explicit limitation on the number of dimensions. However, when using ES|QL, the charts seem to return a maximum of three dimensions. (am I missing something?)

Screen.Recording.2026-01-07.at.1.38.14.PM.mov

Additionally, with the following query:

FROM metrics-*
| STATS AVG(metrics.system.cpu.load_average.`15m`)
  BY BUCKET(@timestamp, 100, ?_tstart, ?_tend),
     host.ip,
     cloud.region,
     host.id



Screen.Recording.2026-01-07.at.1.39.27.PM.mov

The legend appears in the order cloud.region → host.id → host.ip, as shown in recording above.
How is the legend order determined?

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

Labels

backport:skip This PR does not require backporting Feature:Lens release_note:enhancement Team:obs-ux-management Team:Visualizations Team label for Lens, elastic-charts, Graph, legacy editors (TSVB, Visualize, Timelion) t//

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ES|QL] [Lens] We should not suggest pies for many buckets

9 participants