From 940152c1cb632d303040202bf1fe8b7d8db1a30d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 08:15:48 +0000 Subject: [PATCH] docs(ui): state where a dashboard global filter's `field` resolves `## Global Filters` on the dashboards page explained `name`, date defaults and `filterBindings`, but never said where a filter's `field` is looked up. The two halves of the answer were written on the validation reference page in adjacent paragraphs (`content/docs/deployment/validating-metadata.mdx:44-58`) and their asymmetry was never named: dimensions resolve against the dataset, filter fields against the dataset's object, so the same field can be filterable and not group-by-able on one dataset. Adds one section to the page authors actually read when writing `globalFilters`, stating both resolution targets side by side, the two error-severity lint rules that enforce them, the `filterBindings: { name: false }` opt-out, and a caveat scoping the statement to the authorable surface. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8 --- content/docs/ui/dashboards.mdx | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/content/docs/ui/dashboards.mdx b/content/docs/ui/dashboards.mdx index 57481f53ba..4f64e99d1f 100644 --- a/content/docs/ui/dashboards.mdx +++ b/content/docs/ui/dashboards.mdx @@ -360,6 +360,42 @@ under as a dashboard-level variable (readable in widget expressions as `page.`) and the key widgets reference in `filterBindings`. It defaults to `field`; the name `dateRange` is reserved for the built-in date range. +### Where a Filter's `field` Resolves + +A global filter's `field` resolves against the **object behind each bound +widget's dataset** (`dataset.object`) — never against that dataset's declared +`dimensions`. Widget `dimensions` are selected from the dataset **by name**. +They are two different namespaces: + +| Key | Resolved against | Rejected by | +| :--- | :--- | :--- | +| `globalFilters[].field` (after any `filterBindings` re-target) | The bound widget's `dataset.object` — a bare name against that object's own fields, a dotted `relationship.field` path hop by hop with its prefix declared in the dataset's `include` | `dashboard-filter-field-unknown`, severity **error** | +| `widgets[].dimensions[]` | The dataset's declared `dimensions`, by name | `widget-dimension-unknown`, severity **error** | + +Two consequences follow, and their asymmetry is the part that surprises authors: + +- **A filter needs no dataset dimension.** The [Complete Example](#complete-example) + below filters on `project` and `assignee`, neither of which the `project_tasks` + dataset declares — both are fine, because the `project_task` object has those + fields. +- **Declaring a dimension confers no filterability, and a filterable field is not + group-by-able.** The same field can be filterable and un-group-by-able on one + dataset: to filter on it the object must have it, and to group by it the dataset + must declare it as a dimension. Declaring either buys the other nothing. + +A filter naming a field the widget's own object lacks makes that widget's query +address a column that does not exist. `os validate` and the runtime publish door +both refuse the board rather than shipping it — see +[Dangling widget bindings](/docs/deployment/validating-metadata#2-dangling-widget-bindings). +Opt a single widget out with `filterBindings: { : false }` (see +[Per-Widget Filter Bindings](#per-widget-filter-bindings) below). + +This describes the **authorable surface** — what a dashboard may declare, and what +author-time validation accepts. It is not a claim about the analytics query API, +which does accept an object's own field as an ad-hoc dimension without the dataset +declaring it; `widget-dimension-unknown` is what holds the line for authored +dashboards. + ### Date Filter Defaults A `type: 'date'` filter's `defaultValue` must be a value the dashboard can