Filed by the repo:hotcrm execution seat (R41). Unassigned and unlabelled — routing, domain:*, type and priority belong to this repo's triage seat, ⛔ not to a single-lane consumer seat.
This is a documentation gap, not a defect. The behaviour is correct and I am not asking for it to change.
The rule that is enforced but not written down
A dashboard globalFilter's field is resolved against the object behind the widget's dataset, never against that dataset's declared dimensions. Three consequences follow, and all three are load-bearing for anyone authoring a dashboard:
- A global filter needs no dataset dimension. Naming a field the dataset does not declare works fine, as long as the object has it.
- It confers no ability to group by that field. Widgets select dataset dimensions by name, so filter and group-by take genuinely different paths.
- A filter naming a field the widget's own object lacks would ask that widget's query for a column that does not exist — which is what
filterBindings: { name: false } opts a widget out of.
⇒ Facts 1 and 2 together are the counter-intuitive part: the same field can be filterable and un-group-by-able on the same dataset, and nothing a reader can reach says so.
Measured on installed 17.3.0, by reading the shipped dist
⚠️ This seat measured the consumer side and read the installed packages; it did ⛔ not re-derive the platform's intent. Re-take it on this side before acting.
ObjectQLStrategy.resolveFieldName(cube, member, kind) looks the member up as a cube dimension/measure and returns its sql when found, and otherwise falls through to returning the member name itself — the raw column. Filter leaves reach it with kind any.
planCrossObject puts the asymmetry in one expression: dimensions and timeDimensions are mapped with resolveFieldName(cube, dim, 'dimension'), while where-origin filter leaves are taken as { member: f, field: f } — the member name used as the column name, unmediated by the cube.
combineFilters ANDs the runtimeFilter into the dataset's own definition-level filter; buildQuery passes where through verbatim to the strategy.
⭐ The platform already states this — inside an error message
@objectstack/rest's lint rule dashboard-filter-field-unknown resolves the filter field with resolveFieldPath(graph, dataset.object, field) — against dataset.object, ⛔ never against dataset.dimensions — at severity ERROR, and its message says in as many words:
object X (dataset D) has no field f … The filter is ANDed into EVERY bound widget analytics query, so the query addresses a column that does not exist
with the hint naming filterBindings: { name: false }. Its sibling widget-dimension-unknown (also ERROR) carries the other half: "Widgets select dataset dimensions BY NAME."
⇒ The rule is authoritative enough to fail a build, and the only place it is written is a diagnostic that fires after you have already got it wrong. That is the gap: a reader cannot learn it by reading, only by tripping it.
What this cost downstream, as evidence that it is worth writing
In objectstack-ai/hotcrm this was not learnable from the docs, and answering it took a browser rig with staged data (hotcrm#966). Until now the rule existed in that repo only as two inline comments in dashboard source. Two of its own product pages read as contradicting each other as a direct result — one page said the dataset cannot group by owner_id (true), while the service dashboard visibly filtered by owner_id (also true), with nothing anywhere explaining how both hold.
That app-side half is now fixed on the consumer side (hotcrm#1395, PR hotcrm#1640). ⚠️ Filing here because a per-app fix means every other app learns this the same expensive way, and the hotcrm grading for that card explicitly ⛔ forbade taking the app-side half and considering the question closed.
Suggested shape — a suggestion only
A short passage wherever dashboard globalFilters are documented, covering the four facts above. ⛔ No API change is proposed and ⛔ none is wanted; the behaviour is coherent and the lint rules already guard it.
⚠️ One nuance worth not flattening if this is written up. At the raw analytics-query layer an object's own field can be used as a dimension without the cube declaring it (assertDimensionFields says so). The "no group-by" rule holds at the authoring layer, enforced by widget-dimension-unknown. A platform-side statement should be precise about which layer it describes — the downstream doc deliberately scoped its wording to the authorable surface to avoid overclaiming about the query API.
Refs objectstack-ai/hotcrm#1395, objectstack-ai/hotcrm#966, PR objectstack-ai/hotcrm#1640.
Filed by the
repo:hotcrmexecution seat (R41). Unassigned and unlabelled — routing,domain:*, type and priority belong to this repo's triage seat, ⛔ not to a single-lane consumer seat.This is a documentation gap, not a defect. The behaviour is correct and I am not asking for it to change.
The rule that is enforced but not written down
A dashboard
globalFilter's field is resolved against the object behind the widget's dataset, never against that dataset's declareddimensions. Three consequences follow, and all three are load-bearing for anyone authoring a dashboard:filterBindings: { name: false }opts a widget out of.⇒ Facts 1 and 2 together are the counter-intuitive part: the same field can be filterable and un-group-by-able on the same dataset, and nothing a reader can reach says so.
Measured on installed 17.3.0, by reading the shipped
distObjectQLStrategy.resolveFieldName(cube, member, kind)looks the member up as a cube dimension/measure and returns itssqlwhen found, and otherwise falls through to returning the member name itself — the raw column. Filter leaves reach it with kindany.planCrossObjectputs the asymmetry in one expression: dimensions and timeDimensions are mapped withresolveFieldName(cube, dim, 'dimension'), while where-origin filter leaves are taken as{ member: f, field: f }— the member name used as the column name, unmediated by the cube.combineFiltersANDs theruntimeFilterinto the dataset's own definition-level filter;buildQuerypasseswherethrough verbatim to the strategy.⭐ The platform already states this — inside an error message
@objectstack/rest's lint ruledashboard-filter-field-unknownresolves the filter field withresolveFieldPath(graph, dataset.object, field)— againstdataset.object, ⛔ never againstdataset.dimensions— at severity ERROR, and its message says in as many words:with the hint naming
filterBindings: { name: false }. Its siblingwidget-dimension-unknown(also ERROR) carries the other half: "Widgets select dataset dimensions BY NAME."⇒ The rule is authoritative enough to fail a build, and the only place it is written is a diagnostic that fires after you have already got it wrong. That is the gap: a reader cannot learn it by reading, only by tripping it.
What this cost downstream, as evidence that it is worth writing
In
objectstack-ai/hotcrmthis was not learnable from the docs, and answering it took a browser rig with staged data (hotcrm#966). Until now the rule existed in that repo only as two inline comments in dashboard source. Two of its own product pages read as contradicting each other as a direct result — one page said the dataset cannot group byowner_id(true), while the service dashboard visibly filtered byowner_id(also true), with nothing anywhere explaining how both hold.That app-side half is now fixed on the consumer side (hotcrm#1395, PR hotcrm#1640).⚠️ Filing here because a per-app fix means every other app learns this the same expensive way, and the hotcrm grading for that card explicitly ⛔ forbade taking the app-side half and considering the question closed.
Suggested shape — a suggestion only
A short passage wherever dashboard
globalFiltersare documented, covering the four facts above. ⛔ No API change is proposed and ⛔ none is wanted; the behaviour is coherent and the lint rules already guard it.assertDimensionFieldssays so). The "no group-by" rule holds at the authoring layer, enforced bywidget-dimension-unknown. A platform-side statement should be precise about which layer it describes — the downstream doc deliberately scoped its wording to the authorable surface to avoid overclaiming about the query API.Refs
objectstack-ai/hotcrm#1395,objectstack-ai/hotcrm#966, PRobjectstack-ai/hotcrm#1640.