Symptom
A dashboard global filter whose options come from optionsFrom (a live server GROUP BY, rather than a static options list) commits the rendered label as the filter value. Every widget bound to that filter then queries for a value that does not exist and repaints to "No rows".
Static, metadata-declared options are not affected — Revenue Pulse's region filter shows the label EMEA on the trigger and correctly commits the value emea.
Reproduction (2× in the browser, 1× on the API)
- Author a dashboard with an
optionsFrom-sourced select filter and one bound widget:
- Publish, open it as admin, open the Status select and pick In Review.
Expected the widget re-queries with status = 'in_review' and draws 2 tasks.
Actual the widget re-queries with runtimeFilter { "status": "In Review" } and renders "No rows" (0 bars). Same with In Progress.
Confirmed on the API from both sides:
runtimeFilter {status:'In Review'} → {"rows":[]}
runtimeFilter {status:'in_review'} → {"rows":[{"status":"In Review","task_count":2}]}
Root cause
The option-source response carries both forms, and the filter bar reads the wrong one:
rows → server-resolved display labels: [{status:'Backlog'},{status:'Done'},{status:'In Progress'},{status:'In Review'},{status:'To Do'}]
drillRawRows → raw values: [{status:'backlog'},…,{status:'in_review'},{status:'todo'}]
packages/plugin-dashboard/src/DashboardFilterBar.tsx reads r?.[from.valueField] off rows (the dataset branch, ~line 204 on main) and never consults drillRawRows, so the label becomes the committed value.
Sharpening contrast on the same page: the drill path consumes the same response correctly — clicking a Backlog bar issues GET /api/v1/data/showcase_task?filter=["and",…,["status","=","backlog"]] with the raw value. Two consumers of one response; one right, one wrong.
Not a stale-bundle artifact
DashboardFilterBar.tsx's optionsFrom resolution is unchanged between the vendored console pin (6314e87f2d49) and current origin/main (b4d3c2204) — same reads of from.valueField off rows, no drillRawRows consumer on either ref. Checked line by line rather than by diffing whole files.
Suggested shape (not prescriptive)
Pair each option as {value: drillRawRows[i][valueField], label: rows[i][labelField ?? valueField]} so the committed value is always the raw one, and fall back to rows only when drillRawRows is absent. Whatever the shape, the gate that keeps it fixed is a test asserting the committed filter value for an optionsFrom select equals the raw value, not the rendered label — the current tests only assert the option list renders.
Source
Found by the platform checklist retest of dashboards.global-filters-rescope (framework 279ee48a, console pin 6314e87f2d49).
Symptom
A dashboard global filter whose options come from
optionsFrom(a live server GROUP BY, rather than a staticoptionslist) commits the rendered label as the filter value. Every widget bound to that filter then queries for a value that does not exist and repaints to "No rows".Static, metadata-declared options are not affected — Revenue Pulse's
regionfilter shows the labelEMEAon the trigger and correctly commits the valueemea.Reproduction (2× in the browser, 1× on the API)
optionsFrom-sourced select filter and one bound widget:globalFilters: [{ name: 'st', field: 'status', label: 'Status', type: 'select', scope: 'dashboard', optionsFrom: { object: 'showcase_task', valueField: 'status', labelField: 'status' }, }] widgets: [{ type: 'bar', dataset: 'showcase_task_metrics', dimensions: ['status'], values: ['task_count'] }]Expected the widget re-queries with
status = 'in_review'and draws 2 tasks.Actual the widget re-queries with
runtimeFilter { "status": "In Review" }and renders "No rows" (0 bars). Same with In Progress.Confirmed on the API from both sides:
runtimeFilter {status:'In Review'}→{"rows":[]}runtimeFilter {status:'in_review'}→{"rows":[{"status":"In Review","task_count":2}]}Root cause
The option-source response carries both forms, and the filter bar reads the wrong one:
rows→ server-resolved display labels:[{status:'Backlog'},{status:'Done'},{status:'In Progress'},{status:'In Review'},{status:'To Do'}]drillRawRows→ raw values:[{status:'backlog'},…,{status:'in_review'},{status:'todo'}]packages/plugin-dashboard/src/DashboardFilterBar.tsxreadsr?.[from.valueField]offrows(the dataset branch, ~line 204 onmain) and never consultsdrillRawRows, so the label becomes the committed value.Sharpening contrast on the same page: the drill path consumes the same response correctly — clicking a
Backlogbar issuesGET /api/v1/data/showcase_task?filter=["and",…,["status","=","backlog"]]with the raw value. Two consumers of one response; one right, one wrong.Not a stale-bundle artifact
DashboardFilterBar.tsx'soptionsFromresolution is unchanged between the vendored console pin (6314e87f2d49) and currentorigin/main(b4d3c2204) — same reads offrom.valueFieldoffrows, nodrillRawRowsconsumer on either ref. Checked line by line rather than by diffing whole files.Suggested shape (not prescriptive)
Pair each option as
{value: drillRawRows[i][valueField], label: rows[i][labelField ?? valueField]}so the committed value is always the raw one, and fall back torowsonly whendrillRawRowsis absent. Whatever the shape, the gate that keeps it fixed is a test asserting the committed filter value for anoptionsFromselect equals the raw value, not the rendered label — the current tests only assert the option list renders.Source
Found by the platform checklist retest of
dashboards.global-filters-rescope(framework279ee48a, console pin6314e87f2d49).