[Discover Sessions as Code] Discover search embeddable: API schema, transforms, and feature flag#255213
Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (6)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
… filter integration - Fix type error in get_legacy_log_stream_embeddable_factory.ts: convert API-format sort/columns to stored format using toStoredSort/toStoredColumns - Fix type error in get_search_embeddable_factory.tsx: replace stored-format comparator keys with API-format keys (snake_case) in initializeUnsavedChanges - Fix toStoredSearchEmbeddableState to not include empty grid objects - Add full round-trip tests (stored → API → stored) for by-value state including filters, query, columns, sort, grid, and row heights - Add round-trip test for by-reference state with panel overrides - Add filter-specific round-trip integration tests covering phrase, phrases, exists, range, DSL, and combined (group) filter types - Update search_embeddable_transforms tests for new API-format expectations Resolves remaining tasks from elastic#255213 for elastic#248927. Made-with: Cursor
… filter integration - Fix type error in get_legacy_log_stream_embeddable_factory.ts: convert API-format sort/columns to stored format using toStoredSort/toStoredColumns - Fix type error in get_search_embeddable_factory.tsx: replace stored-format comparator keys with API-format keys (snake_case) in initializeUnsavedChanges - Fix toStoredSearchEmbeddableState to not include empty grid objects - Add full round-trip tests (stored → API → stored) for by-value state including filters, query, columns, sort, grid, and row heights - Add round-trip test for by-reference state with panel overrides - Add filter-specific round-trip integration tests covering phrase, phrases, exists, range, DSL, and combined (group) filter types - Update search_embeddable_transforms tests for new API-format expectations Resolves remaining tasks from elastic#255213 for elastic#248927. Made-with: Cursor
…hema
- By-value: persist panel state in tabs[0] only. Transforms merge stored
panel overrides into the single tab (stored→API) and read from tabs[0]
when building stored state (API→stored). No top-level overrides.
- By-reference: persist panel overrides under `overrides`. Transforms
read/write overrides instead of top-level sort, columns, row_height, etc.
Map selected_tab_id ↔ selectedTabId in stored state.
- Deserialization: by-ref uses serializedState.overrides; by-value uses
serializedState.tabs?.[0] for panel overrides.
- Comparators: restrict to DiscoverSessionEmbeddableState (overrides,
selected_tab_id, discover_session_id, tabs). Use deepEquality for
overrides and tabs so unsaved-changes badge works for both modes.
- Add overrides: {} when adding a panel from library (by-ref).
- Tests: add overrides to by-ref fixtures; put sort/columns in overrides;
fix transformIn expected state (grid, selectedTabId); use API shape in
by-value “references stored on dashboard” test.
…tion_tests/ci_checks
davismcphee
left a comment
There was a problem hiding this comment.
Code changes look great overall! I ended up leaving more feedback than I originally intended, but mainly questions and minor suggestions. I also know we're pretty short on time, so none of my feedback is really blocking.
I plan to test it a bunch tomorrow and will report back, but I'll leave the final dev review to @AlexGPlay since you're both closer to the as-code work.
| { meta: { id: 'discoverSessionEmbeddableDataTableSchema' } } | ||
| ); | ||
|
|
||
| const panelOverridesSchema = schema.object( |
There was a problem hiding this comment.
Not important for now, but do you think it would make sense to try and share definitions between the tab state and override state at some point (except maybe meta)?
| let embeddableTransformsEnabled = false; | ||
| core | ||
| .getStartServices() | ||
| .then(([{ featureFlags }]) => { | ||
| featureFlags | ||
| .getBooleanValue$(EMBEDDABLE_TRANSFORMS_FEATURE_FLAG_KEY, embeddableTransformsEnabled) | ||
| .subscribe((value) => { | ||
| embeddableTransformsEnabled = value; | ||
| }); | ||
| }) | ||
| .catch((error) => { | ||
| this.logger.error(error); | ||
| }); |
There was a problem hiding this comment.
In this case maybe we can just call featureFlags.getBooleanValue() directly where it's used below?
davismcphee
left a comment
There was a problem hiding this comment.
Great job on this! I tested it pretty thoroughly today and encountered almost no issues with the feature flag enabled or disabled. I left comments for the couple of things I noticed, but nothing that should block this PR imo.
This is what I did for testing:
- Create and save a dashboard in main with various Discover panels
- Switch to this branch, for both feature flag enabled and disabled:
- Open the dashboard from main and confirm all Discover panels are correct
- Use "save as" to save a copy of the dashboard
- Recreate the same dashboard from scratch and save it
- With the feature flag enabled, fetch all of the created dashboards from the API and diff them to confirm they are all identical apart from IDs
Copy of the dashboard I used for testing
{
"options": {
"hide_panel_titles": false,
"hide_panel_borders": false,
"use_margins": true,
"auto_apply_filters": true,
"sync_colors": false,
"sync_cursor": true,
"sync_tooltips": false
},
"query": {
"query": "",
"language": "kuery"
},
"time_range": {
"from": "now-7d/d",
"to": "now"
},
"title": "test",
"panels": [
{
"grid": {
"x": 0,
"y": 0,
"w": 24,
"h": 15
},
"config": {
"title": "ES|QL by ref",
"description": "",
"discover_session_id": "05479245-fab6-4950-905a-ebead2c16a4a",
"selected_tab_id": "75dc317c-0fb1-4c43-ac85-0d3e64fb0675",
"overrides": {}
},
"uid": "f0f23a51-ed74-4e42-a112-779fd0f8cce6",
"type": "discover_session"
},
{
"grid": {
"x": 0,
"y": 15,
"w": 24,
"h": 15
},
"config": {
"title": "Classic by ref",
"description": "",
"discover_session_id": "05479245-fab6-4950-905a-ebead2c16a4a",
"selected_tab_id": "7240f69c-462c-45ba-8a11-5cb484516a53",
"overrides": {}
},
"uid": "7d00cbe9-1a0f-49e2-8a54-8924a2be7273",
"type": "discover_session"
},
{
"grid": {
"x": 24,
"y": 0,
"w": 24,
"h": 15
},
"config": {
"title": "ES|QL by val",
"tabs": [
{
"sort": [
{
"name": "agent",
"direction": "asc"
}
],
"column_order": [
"agent",
"extension",
"message"
],
"header_row_height": 3,
"density": "compact",
"query": {
"esql": "FROM kibana_sample_data_logs"
}
}
]
},
"uid": "60e16969-3da8-4781-827e-b7288224fff7",
"type": "discover_session"
},
{
"grid": {
"x": 24,
"y": 15,
"w": 24,
"h": 15
},
"config": {
"title": "Classic by val",
"tabs": [
{
"sort": [
{
"name": "timestamp",
"direction": "desc"
},
{
"name": "agent.keyword",
"direction": "asc"
}
],
"column_order": [
"agent.keyword",
"extension",
"message"
],
"header_row_height": 3,
"density": "compact",
"query": {
"query": "bytes > 500",
"language": "kuery"
},
"filters": [],
"data_source": {
"type": "data_view_reference",
"id": "90943e30-9a47-11e8-b64d-95841ca0b247"
},
"view_mode": "documents"
}
]
},
"uid": "6970a0fd-5a44-4240-a421-63a689767173",
"type": "discover_session"
},
{
"grid": {
"x": 0,
"y": 30,
"w": 24,
"h": 15
},
"config": {
"title": "ES|QL with overrides",
"description": "",
"discover_session_id": "05479245-fab6-4950-905a-ebead2c16a4a",
"selected_tab_id": "75dc317c-0fb1-4c43-ac85-0d3e64fb0675",
"overrides": {
"sort": [],
"column_order": [
"message",
"agent",
"extension"
],
"row_height": "auto"
}
},
"uid": "ab861227-0e98-422d-b5a9-463adaef39aa",
"type": "discover_session"
},
{
"grid": {
"x": 24,
"y": 30,
"w": 24,
"h": 15
},
"config": {
"title": "Classic with overrides",
"description": "",
"discover_session_id": "05479245-fab6-4950-905a-ebead2c16a4a",
"selected_tab_id": "7240f69c-462c-45ba-8a11-5cb484516a53",
"overrides": {
"sort": [],
"column_order": [
"message",
"agent.keyword",
"extension"
]
}
},
"uid": "f647cbc4-efdc-477e-907a-80096b973ef4",
"type": "discover_session"
}
],
"pinned_panels": [],
"access_control": {
"access_mode": "default"
}
}You can consider this approved on my end! But I'll leave the final official approval to @AlexGPlay.
…hub.com:lukasolson/kibana into discover_sessions_as_code/embeddable_transforms
| export function fromStoredTab( | ||
| tab: DiscoverSessionTabAttributes, | ||
| references: SavedObjectReference[] = [] | ||
| ): DiscoverSessionTab { | ||
| const { | ||
| sort, | ||
| sampleSize, | ||
| rowsPerPage, | ||
| headerRowHeight, | ||
| density, | ||
| viewMode, | ||
| kibanaSavedObjectMeta: { searchSourceJSON }, | ||
| } = tab; | ||
| const apiTab = { | ||
| ...toDiscoverSessionPanelOverrides(tab), | ||
| sort: fromStoredSort(sort), | ||
| header_row_height: fromStoredHeight(headerRowHeight), | ||
| density: density ?? DataGridDensity.COMPACT, | ||
| }; | ||
| const searchSourceValues = parseSearchSourceJSON(searchSourceJSON); | ||
| const { index, query, filter } = injectReferences(searchSourceValues, references); | ||
| return isOfAggregateQueryType(query) | ||
| ? { ...apiTab, query } | ||
| : { | ||
| ...apiTab, | ||
| ...(sampleSize && { sample_size: sampleSize }), | ||
| ...(rowsPerPage && { rows_per_page: rowsPerPage }), | ||
| query, | ||
| filters: fromStoredFilters(filter) ?? [], | ||
| data_source: fromStoredDataView(index), | ||
| view_mode: viewMode ?? VIEW_MODE.DOCUMENT_LEVEL, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🟢 Low embeddable/transform_utils.ts:178
fromStoredTab calls fromStoredSort(sort) without checking if sort is defined, and fromStoredSort immediately calls sort.map(). When tab.sort is undefined, this throws TypeError: Cannot read properties of undefined (reading 'map'). Other call sites guard this with sort && checks, confirming the field is optional. Consider adding a null check before invoking fromStoredSort.
const apiTab = {
...toDiscoverSessionPanelOverrides(tab),
- sort: fromStoredSort(sort),
+ sort: sort ? fromStoredSort(sort) : [],
header_row_height: fromStoredHeight(headerRowHeight),
density: density ?? DataGridDensity.COMPACT,
};🤖 Copy this AI Prompt to have your agent fix this:
In file src/platform/plugins/shared/discover/common/embeddable/transform_utils.ts around lines 178-210:
`fromStoredTab` calls `fromStoredSort(sort)` without checking if `sort` is defined, and `fromStoredSort` immediately calls `sort.map()`. When `tab.sort` is undefined, this throws `TypeError: Cannot read properties of undefined (reading 'map')`. Other call sites guard this with `sort &&` checks, confirming the field is optional. Consider adding a null check before invoking `fromStoredSort`.
Evidence trail:
src/platform/plugins/shared/discover/common/embeddable/transform_utils.ts lines 178-193 (fromStoredTab calling fromStoredSort without guard), line 242 (guarded call with `sort &&`), lines 291-299 (fromStoredSort definition calling sort.map() directly). Schema at src/platform/plugins/shared/saved_search/server/saved_objects/schema.ts lines 146-148 (DISCOVER_SESSION_TAB_ATTRIBUTES definition), line 170 showing `sort: schema.maybe(sort)` in VERSION_7.
iblancof
left a comment
There was a problem hiding this comment.
Code changes in src/platform/packages/shared/kbn-saved-search-component/src/components/saved_search.tsx LGTM
AlexGPlay
left a comment
There was a problem hiding this comment.
lgtm - there are a lot of things going on so hopefully with Davis taking a look too nothing escaped the review 🧐
…hub.com:lukasolson/kibana into discover_sessions_as_code/embeddable_transforms
💔 Build Failed
Failed CI StepsMetrics [docs]Module Count
Public APIs missing comments
Async chunks
Public APIs missing exports
Page load bundle
Unknown metric groupsAPI count
async chunk count
History
cc @lukasolson |
…ransforms, and feature flag (elastic#255213) ## Summary Resolves elastic#240164. Resolves elastic#248926. Resolves elastic#248927. Implements **Discover Session search embeddable** support for the Dashboards / embeddables “as code” direction: bidirectional conversion between **persisted Saved Search panel state** (legacy `savedObjectId` / `attributes.tabs` shape) and a **simplified API schema** (`discover_session_id`, `selected_tab_id`, `tabs`, `overrides`, snake_case panel fields, as-code filters and data sources). Registers a **config-schema** for that API shape (when the feature flag is on) so server-side validation and OpenAPI-style docs can describe by-value vs by-reference panels clearly. --- ## Feature flag - **`discover.embeddableTransforms`** (`EMBEDDABLE_TRANSFORMS_FEATURE_FLAG_KEY` in `discover/common/constants.ts`) - **When off (default):** behavior matches pre-PR paths: `transformOut` uses the legacy panel shape; `transformIn` only runs the legacy Saved Search pipeline; **no** embeddable schema is registered from Discover server; client serialization keeps the legacy shape. - **When on:** `transformOut` emits the simplified Discover Session embeddable state; `transformIn` accepts **either** legacy panel state **or** API state and normalizes to stored shape; **Discover server** supplies `getDiscoverSessionEmbeddableSchema`; **client** `serializeState` / `deserializeState` read and write the API types end-to-end for the running embeddable. --- ## Transforms (common) Central logic lives in `discover/common/embeddable/`: | Direction | Role | |-----------|------| | **Stored → API** | `savedSearchToDiscoverSessionEmbeddableState` (+ `byValue*` / `byReference*` helpers) used from `getTransformOut` when the flag is enabled. | | **API → stored** | `discoverSessionToSavedSearchEmbeddableState` (+ helpers) used from `getTransformIn` when input is already API-shaped. | | **Legacy path** | `legacyTransformIn` / `legacyTransformOut` preserve previous behavior for BWC; `SearchEmbeddablePanelApiState` is a **union** of legacy and API state (`types.ts`). | **Type guards** (`type_guards.ts`) distinguish legacy vs API panel state so drilldown transforms and the rest of the pipeline stay correct. **Integration with as-code packages:** - Filters: `fromStoredFilters` / `toStoredFilters` (`@kbn/as-code-filters-transforms`). - Data views / data source: `fromStoredDataView` / `toStoredDataView` (`@kbn/as-code-data-views-transforms`) with a **shared** discriminated data source schema (reference vs inline spec, optional **runtime fields** via `schema_runtime_field`). --- ## By-value vs by-reference semantics - **By-value API:** single-tab model exposed as `tabs` (length 1 today). Panel-level table options merge with that tab on **stored → API**; **API → stored** and client deserialize rebuild Saved Search attributes from `tabs[0]` plus overrides. - **By-reference API:** `discover_session_id`, optional `selected_tab_id`, and **`overrides`** for dashboard-level edits (sort, columns, grid, row heights, sample size, etc.) instead of duplicating those at the top level of the API object. - **Client runtime:** `deserializeState` maps API fields (`discover_session_id` → `savedObjectId`, etc.), applies `overrides` into the runtime Saved Search shape, and keeps title / time range / drilldowns behavior aligned with the publishing layer. --- ## Server: schema registration - `discover/server/embeddable/schema.ts` defines **classic** vs **ES\|QL** tab schemas (`query` + `filters` + `data_source` + `view_mode` vs `aggregateQuerySchema`), panel overrides, drilldown intersection with `serializedTitlesSchema`, `serializedTimeRangeSchema`, and drilldown schema from the embeddable host. - **By-value / by-reference** branches use `BY_VALUE_SCHEMA_META` and `BY_REF_SCHEMA_META` from `@kbn/presentation-publishing-schemas` so `oneOf` / “Any of” documentation shows human-readable option labels. - `DiscoverServerPlugin` registers `getSchema` only when the feature flag is enabled (avoids registering API schema when the feature is off). Exported **TypeOf** types from that schema are the source of truth for `DiscoverSessionEmbeddable*` types consumed by transforms and the public embeddable factory typing. --- ## Client embeddable Previously, by-value Discover search panels could fail to save dashboard-level changes reliably: what we wrote back into the panel JSON didn’t line up with how the embeddable deserialized and compared state, so edits didn’t round-trip cleanly. With `discover.embeddableTransforms` enabled, serialize / deserialize use the same Discover Session API shape as `transformIn` / `transformOut`: by-value state is anchored on `tabs[0]` (with panel fields merged consistently), and serialization emits that shape instead of the older Saved Search–only panel blob. That alignment fixes save-and-return and normal dashboard save for by-value panels so changes actually stick. --- ## Example API calls ```txt --------------------------------------------------------------------------- By reference — minimal (first tab, no overrides) --------------------------------------------------------------------------- POST kbn:/api/dashboards?apiVersion=1 { "title": "Minimal by-ref Discover panel", "panels": [ { "grid": { "x": 0, "y": 0, "w": 48, "h": 15 }, "type": "discover_session", "config": { "title": "Saved session", "discover_session_id": "REPLACE_WITH_SEARCH_SAVED_OBJECT_ID" } } ] } --------------------------------------------------------------------------- By reference — overrides only in API shape (sort, columns, pagination, auto heights) --------------------------------------------------------------------------- # `overrides` can include: column_order, column_settings, sort, density, header_row_height, # row_height (numbers or "auto"), rows_per_page (10|25|50|100|250|500), sample_size (10–10000). POST kbn:/api/dashboards?apiVersion=1 { "title": "By-ref with rich overrides", "panels": [ { "grid": { "x": 0, "y": 0, "w": 48, "h": 18 }, "type": "discover_session", "config": { "title": "Session + dashboard-specific table", "discover_session_id": "REPLACE_WITH_SEARCH_SAVED_OBJECT_ID", "selected_tab_id": "REPLACE_TAB_ID_OPTIONAL", "overrides": { "sort": [{ "name": "@timestamp", "direction": "desc" }], "column_order": ["message", "@timestamp", "host.name"], "column_settings": { "message": { "width": 420 } }, "density": "compact", "header_row_height": "auto", "row_height": "auto", "rows_per_page": 50, "sample_size": 500 } } } ] } --------------------------------------------------------------------------- By value — classic tab with data view reference + view modes (documents / patterns / aggregated) --------------------------------------------------------------------------- # view_mode: "documents" | "patterns" | "aggregated" POST kbn:/api/dashboards?apiVersion=1 { "title": "Inline Discover — patterns mode", "panels": [ { "grid": { "x": 0, "y": 0, "w": 48, "h": 15 }, "type": "discover_session", "config": { "title": "Patterns (by value)", "tabs": [ { "sort": [], "column_order": [], "density": "normal", "header_row_height": 3, "row_height": 3, "rows_per_page": 100, "sample_size": 500, "query": { "query": "service.name : *", "language": "kuery" }, "filters": [], "data_source": { "type": "data_view_reference", "id": "REPLACE_DATA_VIEW_ID" }, "view_mode": "patterns" } ] } } ] } ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…ialization (#261558) ## Summary Relates to #261234 `nonHighlightingFilters` was silently dropped during `fromStoredTab` / `toStoredTab` serialization in the Discover session embeddable as part of [[Discover Sessions as Code] Discover search embeddable: API schema, transforms, and feature flag](#255213). This caused the logs section in the traces flyout to run without the trace context filter (`trace.id`, `transaction.id`, `span.id`), returning all logs in the index instead of only the ones correlated to the opened trace document. |Before|After| |-|-| |<img width="1496" height="965" alt="Screenshot 2026-04-07 at 15 05 34" src="https://github.com/user-attachments/assets/9a1f21cd-3359-4753-b7d6-3bf1e3cef758" />|<img width="1495" height="965" alt="Screenshot 2026-04-07 at 15 03 34" src="https://github.com/user-attachments/assets/87fb9b18-6bef-49dd-94ec-a3eea1d895f8" />| `nonHighlightingFilters` was introduced in [[One Discover] [Logs UX]Add support to disable highlighting for certain filters we deem internal](#239402) to allow certain filters (trace context constraints) to be applied to the ES query without affecting result highlighting. The `fromStoredTab` / `toStoredTab` serialization round-trip was added later in [[Discover Sessions as Code] Discover search embeddable: API schema, transforms, and feature flag](#255213) and never included this field. ~~The fix ensures `nonHighlightingFilters` is read back from the serialized search source in `fromStoredTab` and written into `toStoredTab`, and adds the corresponding field to the server-side schema.~~ The fix avoids exposing `nonHighlightingFilters` as a public API schema field entirely. Since trace context filters are always runtime-derived from the opened document (`traceId`, `spanId`, `transactionId`), they never need to be persisted in the serialized state. The runtime prop path, `nonHighlightingQuery` → `createLogEventsRenderer` → `nonHighlightingFilters` on the initial searchSource, already handles the use case correctly on every mount. A `syncNonHighlightingFilters` effect has also been added to `SavedSearchComponentTable` as a defensive measure, consistent with how other dynamic props (`filters`, `query`, `timeRange`) are synced after mount. A defensive e2e test has been added: it opens the minimal trace flyout (2 correlated logs) and asserts the logs section shows exactly 2 results.
…ialization (elastic#261558) ## Summary Relates to elastic#261234 `nonHighlightingFilters` was silently dropped during `fromStoredTab` / `toStoredTab` serialization in the Discover session embeddable as part of [[Discover Sessions as Code] Discover search embeddable: API schema, transforms, and feature flag](elastic#255213). This caused the logs section in the traces flyout to run without the trace context filter (`trace.id`, `transaction.id`, `span.id`), returning all logs in the index instead of only the ones correlated to the opened trace document. |Before|After| |-|-| |<img width="1496" height="965" alt="Screenshot 2026-04-07 at 15 05 34" src="https://github.com/user-attachments/assets/9a1f21cd-3359-4753-b7d6-3bf1e3cef758" />|<img width="1495" height="965" alt="Screenshot 2026-04-07 at 15 03 34" src="https://github.com/user-attachments/assets/87fb9b18-6bef-49dd-94ec-a3eea1d895f8" />| `nonHighlightingFilters` was introduced in [[One Discover] [Logs UX]Add support to disable highlighting for certain filters we deem internal](elastic#239402) to allow certain filters (trace context constraints) to be applied to the ES query without affecting result highlighting. The `fromStoredTab` / `toStoredTab` serialization round-trip was added later in [[Discover Sessions as Code] Discover search embeddable: API schema, transforms, and feature flag](elastic#255213) and never included this field. ~~The fix ensures `nonHighlightingFilters` is read back from the serialized search source in `fromStoredTab` and written into `toStoredTab`, and adds the corresponding field to the server-side schema.~~ The fix avoids exposing `nonHighlightingFilters` as a public API schema field entirely. Since trace context filters are always runtime-derived from the opened document (`traceId`, `spanId`, `transactionId`), they never need to be persisted in the serialized state. The runtime prop path, `nonHighlightingQuery` → `createLogEventsRenderer` → `nonHighlightingFilters` on the initial searchSource, already handles the use case correctly on every mount. A `syncNonHighlightingFilters` effect has also been added to `SavedSearchComponentTable` as a defensive measure, consistent with how other dynamic props (`filters`, `query`, `timeRange`) are synced after mount. A defensive e2e test has been added: it opens the minimal trace flyout (2 correlated logs) and asserts the logs section shows exactly 2 results. (cherry picked from commit ca6f9ec)
…ab serialization (#261558) (#264345) # Backport This will backport the following commits from `main` to `9.4`: - [[Discover][Traces] Fix nonHighlightingFilters lost in session tab serialization (#261558)](#261558) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Irene Blanco","email":"irene.blanco@elastic.co"},"sourceCommit":{"committedDate":"2026-04-20T09:04:45Z","message":"[Discover][Traces] Fix nonHighlightingFilters lost in session tab serialization (#261558)\n\n## Summary\n\nRelates to https://github.com/elastic/kibana/issues/261234\n\n`nonHighlightingFilters` was silently dropped during `fromStoredTab` /\n`toStoredTab` serialization in the Discover session embeddable as part\nof [[Discover Sessions as Code] Discover search embeddable: API schema,\ntransforms, and feature\nflag](#255213). This caused the\nlogs section in the traces flyout to run without the trace context\nfilter (`trace.id`, `transaction.id`, `span.id`), returning all logs in\nthe index instead of only the ones correlated to the opened trace\ndocument.\n\n|Before|After|\n|-|-|\n|<img width=\"1496\" height=\"965\" alt=\"Screenshot 2026-04-07 at 15 05 34\"\nsrc=\"https://github.com/user-attachments/assets/9a1f21cd-3359-4753-b7d6-3bf1e3cef758\"\n/>|<img width=\"1495\" height=\"965\" alt=\"Screenshot 2026-04-07 at 15 03\n34\"\nsrc=\"https://github.com/user-attachments/assets/87fb9b18-6bef-49dd-94ec-a3eea1d895f8\"\n/>|\n\n`nonHighlightingFilters` was introduced in [[One Discover] [Logs UX]Add\nsupport to disable highlighting for certain filters we deem\ninternal](#239402) to allow\ncertain filters (trace context constraints) to be applied to the ES\nquery without affecting result highlighting. The `fromStoredTab` /\n`toStoredTab` serialization round-trip was added later in [[Discover\nSessions as Code] Discover search embeddable: API schema, transforms,\nand feature flag](#255213) and\nnever included this field.\n\n~~The fix ensures `nonHighlightingFilters` is read back from the\nserialized search source in `fromStoredTab` and written into\n`toStoredTab`, and adds the corresponding field to the server-side\nschema.~~\n\nThe fix avoids exposing `nonHighlightingFilters` as a public API schema\nfield entirely. Since trace context filters are always runtime-derived\nfrom the opened document (`traceId`, `spanId`, `transactionId`), they\nnever need to be persisted in the serialized state.\n\nThe runtime prop path, `nonHighlightingQuery` →\n`createLogEventsRenderer` → `nonHighlightingFilters` on the initial\nsearchSource, already handles the use case correctly on every mount.\n\nA `syncNonHighlightingFilters` effect has also been added to\n`SavedSearchComponentTable` as a defensive measure, consistent with how\nother dynamic props (`filters`, `query`, `timeRange`) are synced after\nmount.\n\nA defensive e2e test has been added: it opens the minimal trace flyout\n(2 correlated logs) and asserts the logs section shows exactly 2\nresults.","sha":"ca6f9ec3b2ab13572f5fe7144537cb232870f930","branchLabelMapping":{"^v9.5.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","backport:version","Feature:Traces in Discover","Team:obs-exploration","v9.4.0","v9.5.0"],"title":"[Discover][Traces] Fix nonHighlightingFilters lost in session tab serialization","number":261558,"url":"https://github.com/elastic/kibana/pull/261558","mergeCommit":{"message":"[Discover][Traces] Fix nonHighlightingFilters lost in session tab serialization (#261558)\n\n## Summary\n\nRelates to https://github.com/elastic/kibana/issues/261234\n\n`nonHighlightingFilters` was silently dropped during `fromStoredTab` /\n`toStoredTab` serialization in the Discover session embeddable as part\nof [[Discover Sessions as Code] Discover search embeddable: API schema,\ntransforms, and feature\nflag](#255213). This caused the\nlogs section in the traces flyout to run without the trace context\nfilter (`trace.id`, `transaction.id`, `span.id`), returning all logs in\nthe index instead of only the ones correlated to the opened trace\ndocument.\n\n|Before|After|\n|-|-|\n|<img width=\"1496\" height=\"965\" alt=\"Screenshot 2026-04-07 at 15 05 34\"\nsrc=\"https://github.com/user-attachments/assets/9a1f21cd-3359-4753-b7d6-3bf1e3cef758\"\n/>|<img width=\"1495\" height=\"965\" alt=\"Screenshot 2026-04-07 at 15 03\n34\"\nsrc=\"https://github.com/user-attachments/assets/87fb9b18-6bef-49dd-94ec-a3eea1d895f8\"\n/>|\n\n`nonHighlightingFilters` was introduced in [[One Discover] [Logs UX]Add\nsupport to disable highlighting for certain filters we deem\ninternal](#239402) to allow\ncertain filters (trace context constraints) to be applied to the ES\nquery without affecting result highlighting. The `fromStoredTab` /\n`toStoredTab` serialization round-trip was added later in [[Discover\nSessions as Code] Discover search embeddable: API schema, transforms,\nand feature flag](#255213) and\nnever included this field.\n\n~~The fix ensures `nonHighlightingFilters` is read back from the\nserialized search source in `fromStoredTab` and written into\n`toStoredTab`, and adds the corresponding field to the server-side\nschema.~~\n\nThe fix avoids exposing `nonHighlightingFilters` as a public API schema\nfield entirely. Since trace context filters are always runtime-derived\nfrom the opened document (`traceId`, `spanId`, `transactionId`), they\nnever need to be persisted in the serialized state.\n\nThe runtime prop path, `nonHighlightingQuery` →\n`createLogEventsRenderer` → `nonHighlightingFilters` on the initial\nsearchSource, already handles the use case correctly on every mount.\n\nA `syncNonHighlightingFilters` effect has also been added to\n`SavedSearchComponentTable` as a defensive measure, consistent with how\nother dynamic props (`filters`, `query`, `timeRange`) are synced after\nmount.\n\nA defensive e2e test has been added: it opens the minimal trace flyout\n(2 correlated logs) and asserts the logs section shows exactly 2\nresults.","sha":"ca6f9ec3b2ab13572f5fe7144537cb232870f930"}},"sourceBranch":"main","suggestedTargetBranches":["9.4"],"targetPullRequestStates":[{"branch":"9.4","label":"v9.4.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.5.0","branchLabelMappingKey":"^v9.5.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/261558","number":261558,"mergeCommit":{"message":"[Discover][Traces] Fix nonHighlightingFilters lost in session tab serialization (#261558)\n\n## Summary\n\nRelates to https://github.com/elastic/kibana/issues/261234\n\n`nonHighlightingFilters` was silently dropped during `fromStoredTab` /\n`toStoredTab` serialization in the Discover session embeddable as part\nof [[Discover Sessions as Code] Discover search embeddable: API schema,\ntransforms, and feature\nflag](#255213). This caused the\nlogs section in the traces flyout to run without the trace context\nfilter (`trace.id`, `transaction.id`, `span.id`), returning all logs in\nthe index instead of only the ones correlated to the opened trace\ndocument.\n\n|Before|After|\n|-|-|\n|<img width=\"1496\" height=\"965\" alt=\"Screenshot 2026-04-07 at 15 05 34\"\nsrc=\"https://github.com/user-attachments/assets/9a1f21cd-3359-4753-b7d6-3bf1e3cef758\"\n/>|<img width=\"1495\" height=\"965\" alt=\"Screenshot 2026-04-07 at 15 03\n34\"\nsrc=\"https://github.com/user-attachments/assets/87fb9b18-6bef-49dd-94ec-a3eea1d895f8\"\n/>|\n\n`nonHighlightingFilters` was introduced in [[One Discover] [Logs UX]Add\nsupport to disable highlighting for certain filters we deem\ninternal](#239402) to allow\ncertain filters (trace context constraints) to be applied to the ES\nquery without affecting result highlighting. The `fromStoredTab` /\n`toStoredTab` serialization round-trip was added later in [[Discover\nSessions as Code] Discover search embeddable: API schema, transforms,\nand feature flag](#255213) and\nnever included this field.\n\n~~The fix ensures `nonHighlightingFilters` is read back from the\nserialized search source in `fromStoredTab` and written into\n`toStoredTab`, and adds the corresponding field to the server-side\nschema.~~\n\nThe fix avoids exposing `nonHighlightingFilters` as a public API schema\nfield entirely. Since trace context filters are always runtime-derived\nfrom the opened document (`traceId`, `spanId`, `transactionId`), they\nnever need to be persisted in the serialized state.\n\nThe runtime prop path, `nonHighlightingQuery` →\n`createLogEventsRenderer` → `nonHighlightingFilters` on the initial\nsearchSource, already handles the use case correctly on every mount.\n\nA `syncNonHighlightingFilters` effect has also been added to\n`SavedSearchComponentTable` as a defensive measure, consistent with how\nother dynamic props (`filters`, `query`, `timeRange`) are synced after\nmount.\n\nA defensive e2e test has been added: it opens the minimal trace flyout\n(2 correlated logs) and asserts the logs section shows exactly 2\nresults.","sha":"ca6f9ec3b2ab13572f5fe7144537cb232870f930"}}]}] BACKPORT--> Co-authored-by: Irene Blanco <irene.blanco@elastic.co>
Summary
Resolves #240164.
Resolves #248926.
Resolves #248927.
Implements Discover Session search embeddable support for the Dashboards / embeddables “as code” direction: bidirectional conversion between persisted Saved Search panel state (legacy
savedObjectId/attributes.tabsshape) and a simplified API schema (discover_session_id,selected_tab_id,tabs,overrides, snake_case panel fields, as-code filters and data sources). Registers a config-schema for that API shape (when the feature flag is on) so server-side validation and OpenAPI-style docs can describe by-value vs by-reference panels clearly.Feature flag
discover.embeddableTransforms(EMBEDDABLE_TRANSFORMS_FEATURE_FLAG_KEYindiscover/common/constants.ts)transformOutuses the legacy panel shape;transformInonly runs the legacy Saved Search pipeline; no embeddable schema is registered from Discover server; client serialization keeps the legacy shape.transformOutemits the simplified Discover Session embeddable state;transformInaccepts either legacy panel state or API state and normalizes to stored shape; Discover server suppliesgetDiscoverSessionEmbeddableSchema; clientserializeState/deserializeStateread and write the API types end-to-end for the running embeddable.Transforms (common)
Central logic lives in
discover/common/embeddable/:savedSearchToDiscoverSessionEmbeddableState(+byValue*/byReference*helpers) used fromgetTransformOutwhen the flag is enabled.discoverSessionToSavedSearchEmbeddableState(+ helpers) used fromgetTransformInwhen input is already API-shaped.legacyTransformIn/legacyTransformOutpreserve previous behavior for BWC;SearchEmbeddablePanelApiStateis a union of legacy and API state (types.ts).Type guards (
type_guards.ts) distinguish legacy vs API panel state so drilldown transforms and the rest of the pipeline stay correct.Integration with as-code packages:
fromStoredFilters/toStoredFilters(@kbn/as-code-filters-transforms).fromStoredDataView/toStoredDataView(@kbn/as-code-data-views-transforms) with a shared discriminated data source schema (reference vs inline spec, optional runtime fields viaschema_runtime_field).By-value vs by-reference semantics
tabs(length 1 today). Panel-level table options merge with that tab on stored → API; API → stored and client deserialize rebuild Saved Search attributes fromtabs[0]plus overrides.discover_session_id, optionalselected_tab_id, andoverridesfor dashboard-level edits (sort, columns, grid, row heights, sample size, etc.) instead of duplicating those at the top level of the API object.deserializeStatemaps API fields (discover_session_id→savedObjectId, etc.), appliesoverridesinto the runtime Saved Search shape, and keeps title / time range / drilldowns behavior aligned with the publishing layer.Server: schema registration
discover/server/embeddable/schema.tsdefines classic vs ES|QL tab schemas (query+filters+data_source+view_modevsaggregateQuerySchema), panel overrides, drilldown intersection withserializedTitlesSchema,serializedTimeRangeSchema, and drilldown schema from the embeddable host.BY_VALUE_SCHEMA_METAandBY_REF_SCHEMA_METAfrom@kbn/presentation-publishing-schemassooneOf/ “Any of” documentation shows human-readable option labels.DiscoverServerPluginregistersgetSchemaonly when the feature flag is enabled (avoids registering API schema when the feature is off).Exported TypeOf types from that schema are the source of truth for
DiscoverSessionEmbeddable*types consumed by transforms and the public embeddable factory typing.Client embeddable
Previously, by-value Discover search panels could fail to save dashboard-level changes reliably: what we wrote back into the panel JSON didn’t line up with how the embeddable deserialized and compared state, so edits didn’t round-trip cleanly.
With
discover.embeddableTransformsenabled, serialize / deserialize use the same Discover Session API shape astransformIn/transformOut: by-value state is anchored ontabs[0](with panel fields merged consistently), and serialization emits that shape instead of the older Saved Search–only panel blob. That alignment fixes save-and-return and normal dashboard save for by-value panels so changes actually stick.Example API calls
--------------------------------------------------------------------------- By reference — minimal (first tab, no overrides) --------------------------------------------------------------------------- POST kbn:/api/dashboards?apiVersion=1 { "title": "Minimal by-ref Discover panel", "panels": [ { "grid": { "x": 0, "y": 0, "w": 48, "h": 15 }, "type": "discover_session", "config": { "title": "Saved session", "discover_session_id": "REPLACE_WITH_SEARCH_SAVED_OBJECT_ID" } } ] } --------------------------------------------------------------------------- By reference — overrides only in API shape (sort, columns, pagination, auto heights) --------------------------------------------------------------------------- # `overrides` can include: column_order, column_settings, sort, density, header_row_height, # row_height (numbers or "auto"), rows_per_page (10|25|50|100|250|500), sample_size (10–10000). POST kbn:/api/dashboards?apiVersion=1 { "title": "By-ref with rich overrides", "panels": [ { "grid": { "x": 0, "y": 0, "w": 48, "h": 18 }, "type": "discover_session", "config": { "title": "Session + dashboard-specific table", "discover_session_id": "REPLACE_WITH_SEARCH_SAVED_OBJECT_ID", "selected_tab_id": "REPLACE_TAB_ID_OPTIONAL", "overrides": { "sort": [{ "name": "@timestamp", "direction": "desc" }], "column_order": ["message", "@timestamp", "host.name"], "column_settings": { "message": { "width": 420 } }, "density": "compact", "header_row_height": "auto", "row_height": "auto", "rows_per_page": 50, "sample_size": 500 } } } ] } --------------------------------------------------------------------------- By value — classic tab with data view reference + view modes (documents / patterns / aggregated) --------------------------------------------------------------------------- # view_mode: "documents" | "patterns" | "aggregated" POST kbn:/api/dashboards?apiVersion=1 { "title": "Inline Discover — patterns mode", "panels": [ { "grid": { "x": 0, "y": 0, "w": 48, "h": 15 }, "type": "discover_session", "config": { "title": "Patterns (by value)", "tabs": [ { "sort": [], "column_order": [], "density": "normal", "header_row_height": 3, "row_height": 3, "rows_per_page": 100, "sample_size": 500, "query": { "query": "service.name : *", "language": "kuery" }, "filters": [], "data_source": { "type": "data_view_reference", "id": "REPLACE_DATA_VIEW_ID" }, "view_mode": "patterns" } ] } } ] }