Measured while implementing #6864 on origin/main @ ce45a0306. Different code path, different failure mode, so filed separately rather than ridden into that PR. ⛔ Nothing was changed; this is a source read.
The measurement
ObjectChart.runAggregate (packages/plugin-charts/src/ObjectChart.tsx:597-613) knows about two authored aggregate.groupBy shapes and routes them to two different wires:
const gb = schema.aggregate.groupBy as any;
// Structured GroupBy node (e.g. `{ field, dateGranularity: 'day' }`)
// requires the spec-shape `{ groupBy: GroupByNode[], aggregations, where }`
// payload so the server-side date-bucket engine kicks in.
// The legacy `{ field, function, groupBy, filter }` cube/analytics
// path does NOT honour `dateGranularity`.
const isStructured = gb && typeof gb === 'object' && !Array.isArray(gb);
if (isStructured) { /* spec-shape call */ }
/* else: legacy `{ field, function, groupBy: gb, filter }` call */
The METRIC path has no such branch. ObjectMetricWidget.computeOne (packages/plugin-dashboard/src/ObjectMetricWidget.tsx:246-252) forwards the authored value straight through:
const results = await ds.aggregate(objectName, {
field: aggregate.field,
function: aggregate.function,
groupBy: aggregate.groupBy || '_all',
filter: filterForRun,
});
grep over that file for dateGranularity, isStructured and aggregations returns zero hits, so there is no structured handling anywhere else in it.
What that does on the wire
A structured node is an object and not an array, so looksLikeSpecShape is false and the call takes ObjectStackAdapter.aggregate's ANALYTICS branch, which builds
dimensions: params.groupBy && params.groupBy !== '_all' ? [params.groupBy] : []
⇒ the POST body carries dimensions: [{ field: 'closed_at', dateGranularity: 'month' }] — an object where the analytics contract declares a dimension NAME. By the comment quoted above, that path does not honour dateGranularity in the first place, so even if the server tolerated the shape the bucketing the author asked for would not happen.
Why the authored shape is real, not hypothetical
{ field, dateGranularity } and { field, alias } are shapes this repo already recognises as authored aggregate.groupBy:
packages/core/src/utils/chart-category-key.ts — chartCategoryKey resolves the category column for exactly those node shapes;
packages/core/src/utils/chart-category-key.test.ts:45 and :56 pin them;
packages/plugin-charts/src/ObjectChart.absentCategoryAxisRefusal-8168.test.tsx:125 and :350 carry aggregate: { field, function, groupBy: { dateGranularity: 'day' } };
packages/plugin-dashboard/src/__tests__/DashboardChart.categoryAxisKey-8269.test.tsx:154 and :158 carry the same shape in widget metadata.
Both relays hand the metric family the raw authored value: DashboardRenderer.tsx:722 and DashboardGridLayout.tsx:301 both build aggregate: { field: providerAgg.field, function: providerAgg.function, groupBy: providerAgg.groupBy } for the object-metric node.
Why the type system does not catch it
ObjectMetricWidget's own props declare aggregate?: { field: string; function: string; groupBy?: string } (:44) — a STRING. But the value reaching it crosses an any seam twice: isObjectProvider narrows to aggregate?: any (packages/plugin-dashboard/src/utils.ts:10), and computeOne(ds: any, …) takes the datasource untyped. So the declared string refuses nothing at runtime and nothing at compile time either.
Reachability: NOT established
Same posture as #6825 and #6864. Every one of the sites listed above is a TEST FIXTURE; a sweep of tracked .json/.ts/.tsx/.mdx/.md/.yaml/.yml (6557 files, with a lit control on the same command shape returning 208 lines across 103 files) found no shipped app metadata declaring a structured aggregate.groupBy on a metric widget. So this is latent, and I am NOT claiming a live defect — recording it because the severity judgement is triage's, not mine.
Relationship to #6864
#6864 is the ARRAY form on the same authored key: it selects the adapter's spec-shape branch and the legacy keys vanish. This is the OBJECT form on the metric path, which selects the analytics branch and sends the node itself as a dimension. #6864's fix (PR #8612) refuses the array case at the producer and deliberately does not touch this one — an object groupBy never reaches the spec-shape branch, so it is outside that gate by construction.
Refs: #6864 - #6825 / PR #6911 - #8266 / #8269 (the measure and category key contracts that read these same node shapes)
Measured while implementing #6864 on
origin/main@ce45a0306. Different code path, different failure mode, so filed separately rather than ridden into that PR. ⛔ Nothing was changed; this is a source read.The measurement
ObjectChart.runAggregate(packages/plugin-charts/src/ObjectChart.tsx:597-613) knows about two authoredaggregate.groupByshapes and routes them to two different wires:The METRIC path has no such branch.
ObjectMetricWidget.computeOne(packages/plugin-dashboard/src/ObjectMetricWidget.tsx:246-252) forwards the authored value straight through:grepover that file fordateGranularity,isStructuredandaggregationsreturns zero hits, so there is no structured handling anywhere else in it.What that does on the wire
A structured node is an object and not an array, so
looksLikeSpecShapeis false and the call takesObjectStackAdapter.aggregate's ANALYTICS branch, which builds⇒ the POST body carries
dimensions: [{ field: 'closed_at', dateGranularity: 'month' }]— an object where the analytics contract declares a dimension NAME. By the comment quoted above, that path does not honourdateGranularityin the first place, so even if the server tolerated the shape the bucketing the author asked for would not happen.Why the authored shape is real, not hypothetical
{ field, dateGranularity }and{ field, alias }are shapes this repo already recognises as authoredaggregate.groupBy:packages/core/src/utils/chart-category-key.ts—chartCategoryKeyresolves the category column for exactly those node shapes;packages/core/src/utils/chart-category-key.test.ts:45and:56pin them;packages/plugin-charts/src/ObjectChart.absentCategoryAxisRefusal-8168.test.tsx:125and:350carryaggregate: { field, function, groupBy: { dateGranularity: 'day' } };packages/plugin-dashboard/src/__tests__/DashboardChart.categoryAxisKey-8269.test.tsx:154and:158carry the same shape in widget metadata.Both relays hand the metric family the raw authored value:
DashboardRenderer.tsx:722andDashboardGridLayout.tsx:301both buildaggregate: { field: providerAgg.field, function: providerAgg.function, groupBy: providerAgg.groupBy }for theobject-metricnode.Why the type system does not catch it
ObjectMetricWidget's own props declareaggregate?: { field: string; function: string; groupBy?: string }(:44) — a STRING. But the value reaching it crosses ananyseam twice:isObjectProvidernarrows toaggregate?: any(packages/plugin-dashboard/src/utils.ts:10), andcomputeOne(ds: any, …)takes the datasource untyped. So the declaredstringrefuses nothing at runtime and nothing at compile time either.Reachability: NOT established
Same posture as #6825 and #6864. Every one of the sites listed above is a TEST FIXTURE; a sweep of tracked
.json/.ts/.tsx/.mdx/.md/.yaml/.yml(6557 files, with a lit control on the same command shape returning 208 lines across 103 files) found no shipped app metadata declaring a structuredaggregate.groupByon a metric widget. So this is latent, and I am NOT claiming a live defect — recording it because the severity judgement is triage's, not mine.Relationship to #6864
#6864 is the ARRAY form on the same authored key: it selects the adapter's spec-shape branch and the legacy keys vanish. This is the OBJECT form on the metric path, which selects the analytics branch and sends the node itself as a dimension. #6864's fix (PR #8612) refuses the array case at the producer and deliberately does not touch this one — an object
groupBynever reaches the spec-shape branch, so it is outside that gate by construction.Refs: #6864 - #6825 / PR #6911 - #8266 / #8269 (the measure and category key contracts that read these same node shapes)