You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current implementation injects every dashboard-level AdHoc filter into every Cube query, regardless of which view that query targets (normalizeCubeQuery.ts, getAdHocFilters → validFilters).
Because Cube views are sealed namespaces — every member is fully-qualified by its view (view_a.region, never just region) — applying a filter on view_a.region to a query on view_b results in Cube's /v1/load returning an error on the unknown member.
In practice, any AdHoc filter on a dashboard with panels from more than one view turns the unrelated panels red. Combined with the deduped getTagKeys list (which surfaces region once even if view_a.region and view_b.region are semantically distinct columns), this is both a correctness bug and a UX trap.
Reproduction
Dashboard with two panels: panel A queries view_a (which has view_a.region); panel B queries view_b (no region).
Add an AdHoc filter region = uk (the dropdown shows a single deduped region entry).
Panel A filters correctly. Panel B errors with an unknown-member response from Cube.
Why this is surprisingly subtle
When a user adds region = uk to a multi-panel dashboard, what do they actually mean?
Strict-Cube semantics — "filter view_a.region, the literal column": other views aren't filtered, by design.
Business semantics — "limit the dashboard to UK data, wherever that is represented": filter is conceptually cross-view.
We can't fully deliver business semantics without explicit cross-view equivalence (a Looker LookML "explore"-style construct) declared in the model. So we have to either ship the strict-Cube behaviour and signal it well, or surface the ambiguity in the UI before the filter is created.
Options considered
Option 1 — Drop inapplicable filters at injection time (ship now)
In normalizeCubeQuery, infer the query's view from dimensions ∪ measures ∪ panel-filters (same logic as getViewSelectionState) and drop any AdHoc filter whose member belongs to a different view. Surface dropped filters in the SQL preview ("Skipped N inapplicable AdHoc filters: …").
Pros: small change (~tens of LOC), eliminates the red-panels failure mode immediately. Cons: silent at the dashboard level — users still type `region = uk` expecting global effect. Per-panel hint is the wrong place to surface a dashboard-wide expectation gap.
Sketch:
Build `Map<member, view>` from `metadata.dimensions/measures` (already has `Cube` field on the backend response).
In `normalizeCubeQuery`, filter `adHocFilters` to those whose `key`'s view matches the query's inferred view. If no view can be inferred (no dims/measures/filters yet), skip injection entirely.
Return the dropped list from `normalizeCubeQuery` so `useCubeQueryJson` / `SQLPreview` can show a "skipped N" hint.
Option 2 — One AdHoc filter set per view (medium-term)
Make AdHoc filters explicitly view-scoped at the dashboard schema layer: one `AdHocFiltersVariable` per view, each with its own keys/values fetched from that view only. Click-to-filter from a panel writes to its view's variable.
Pros: kills the semantic mismatch at the root — the user explicitly knows which view they're filtering. Click-to-filter no longer pollutes unrelated panels. Dropdown values are cleanly scoped (no cross-view dimensional collision). Composes with — rather than replacing — Option 1 / getFiltersApplicability. Cons: Grafana's `AdHocFiltersVariable` keys off `{ datasource: { uid } }` (scenes docs) and doesn't currently model "multiple instances on the same datasource UID with a scope qualifier." Implementing this needs either:
(a) one Grafana datasource per view — bad UX for setup;
(b) a custom Scene-level filter UI shipped with the plugin; or
(c) an upstream change to support a `scope`/`group` qualifier on AdHoc variables.
Option 3 — Restrict each dashboard to a single view
Rejected. Violates Grafana's composable-dashboard ethos. Pushes the burden onto data modelers, who'll respond by building giant catch-all super-views. Doesn't help the cross-dashboard filter-carry case in grafana/grafana#108011.
Grafana is adding `getFiltersApplicability` on the datasource interface: the datasource returns which filters apply to which queries, and Grafana's UI greys out the inapplicable ones with a visual cue (instead of "applying everything and getting No Data"). Maps perfectly onto views: a filter is applicable iff its member's view matches the query's view.
Pros: correct runtime behaviour, visual UX feedback, works across dashboard-link filter inheritance for free. Cons: depends on upstream timelines.
Recommendation
Ship Option 1 immediately — eliminates the red-panels failure mode, small surface area.
File a follow-up in grafana/grafana (or grafana/scenes) for upstream AdHocFiltersVariable support for view-scoped filter sets (Option 2), and link / track `getFiltersApplicability` (Option 4).
Adopt getFiltersApplicability when it lands.
Re-evaluate Option 2 once the upstream primitive exists; it remains the best answer to the semantic-mismatch question even after Option 4 ships, because Option 4 is a runtime safety net while Option 2 is a schema-level expression of intent.
Caveat to communicate in docs
Option 1 changes the click-to-filter behaviour the README advertises: clicking customer = BBC on a view_a panel will only filter view_a panels — view_b panels stay unfiltered. That's the correct behaviour given Cube's semantics, but it's a documentation/expectations change.
Problem
The current implementation injects every dashboard-level AdHoc filter into every Cube query, regardless of which view that query targets (
normalizeCubeQuery.ts,getAdHocFilters→validFilters).Because Cube views are sealed namespaces — every member is fully-qualified by its view (
view_a.region, never justregion) — applying a filter onview_a.regionto a query onview_bresults in Cube's/v1/loadreturning an error on the unknown member.In practice, any AdHoc filter on a dashboard with panels from more than one view turns the unrelated panels red. Combined with the deduped
getTagKeyslist (which surfacesregiononce even ifview_a.regionandview_b.regionare semantically distinct columns), this is both a correctness bug and a UX trap.Reproduction
view_a(which hasview_a.region); panel B queriesview_b(noregion).region = uk(the dropdown shows a single dedupedregionentry).Why this is surprisingly subtle
When a user adds
region = ukto a multi-panel dashboard, what do they actually mean?view_a.region, the literal column": other views aren't filtered, by design.We can't fully deliver business semantics without explicit cross-view equivalence (a Looker LookML "explore"-style construct) declared in the model. So we have to either ship the strict-Cube behaviour and signal it well, or surface the ambiguity in the UI before the filter is created.
Options considered
Option 1 — Drop inapplicable filters at injection time (ship now)
In
normalizeCubeQuery, infer the query's view fromdimensions ∪ measures ∪ panel-filters(same logic asgetViewSelectionState) and drop any AdHoc filter whose member belongs to a different view. Surface dropped filters in the SQL preview ("Skipped N inapplicable AdHoc filters: …").Pros: small change (~tens of LOC), eliminates the red-panels failure mode immediately.
Cons: silent at the dashboard level — users still type `region = uk` expecting global effect. Per-panel hint is the wrong place to surface a dashboard-wide expectation gap.
Sketch:
Option 2 — One AdHoc filter set per view (medium-term)
Make AdHoc filters explicitly view-scoped at the dashboard schema layer: one `AdHocFiltersVariable` per view, each with its own keys/values fetched from that view only. Click-to-filter from a panel writes to its view's variable.
Pros: kills the semantic mismatch at the root — the user explicitly knows which view they're filtering. Click-to-filter no longer pollutes unrelated panels. Dropdown values are cleanly scoped (no cross-view dimensional collision). Composes with — rather than replacing — Option 1 /
getFiltersApplicability.Cons: Grafana's `AdHocFiltersVariable` keys off `{ datasource: { uid } }` (scenes docs) and doesn't currently model "multiple instances on the same datasource UID with a scope qualifier." Implementing this needs either:
Option 3 — Restrict each dashboard to a single view
Rejected. Violates Grafana's composable-dashboard ethos. Pushes the burden onto data modelers, who'll respond by building giant catch-all super-views. Doesn't help the cross-dashboard filter-carry case in grafana/grafana#108011.
Option 4 (upstream) — Implement `getFiltersApplicability` (long-term)
Grafana is adding `getFiltersApplicability` on the datasource interface: the datasource returns which filters apply to which queries, and Grafana's UI greys out the inapplicable ones with a visual cue (instead of "applying everything and getting
No Data"). Maps perfectly onto views: a filter is applicable iff its member's view matches the query's view.Pros: correct runtime behaviour, visual UX feedback, works across dashboard-link filter inheritance for free.
Cons: depends on upstream timelines.
Recommendation
grafana/grafana(orgrafana/scenes) for upstreamAdHocFiltersVariablesupport for view-scoped filter sets (Option 2), and link / track `getFiltersApplicability` (Option 4).getFiltersApplicabilitywhen it lands.Caveat to communicate in docs
Option 1 changes the click-to-filter behaviour the README advertises: clicking
customer = BBCon aview_apanel will only filterview_apanels —view_bpanels stay unfiltered. That's the correct behaviour given Cube's semantics, but it's a documentation/expectations change.Related
templateSrv.getAdhocFilters(touches the same code path)getFiltersApplicability(Option 4)