Skip to content

Commit 8dbd2a8

Browse files
os-zhuangclaude
andauthored
fix(service-analytics): dataset fields describe dimension columns on the measure-filter merge path (#5537) (#5691)
A dataset selection whose base measures ALL carry their own measure-scoped `filter` (or whose selected derived measures depend only on such measures) never issues the primary grouped query: `runMeasurePass` starts from a synthesized `{ rows: [], fields: [] }` and each supplementary sub-query appends exactly one MEASURE descriptor. The dimension columns are in every row — they are the merge key — but described nowhere, so a consumer reading column metadata had no `label` and no `type` for the grouped column and fell back to humanizing the raw key: "owner" where the dataset declares "Owner". The dimension descriptors are now adopted from the FIRST supplementary result, which projects exactly the dimensions the pass groups by. Taken from the sub-result rather than re-derived from `compiled.cube`: the projected set is defined by each strategy's `buildFieldMeta` (#4033), so an executor-side reconstruction would be a fourth copy of that rule, free to drift from the rows it labels. Reading it back makes the two paths converge by construction — on order (dimensions first), on `type`, and on whatever the active strategy projected. `compareTo`, `totals` and derived measures all route through the same pass, so all three are covered. The label enrichment in `queryDataset` is left enriching-only, with a note saying why: whether a column exists is the query layer's answer, and minting one there would describe a column the rows may not carry. Tests: `dataset-dimension-field-descriptors.test.ts` asserts the complete `fields` set and order (exact `toEqual`, full descriptor) for the healthy single-query path and for every all-filtered shape. Reverse verification — direction predicted first — reverting the executor seam turns the 8 all-filtered cases red and leaves the 4 convergence-target cases green; measured exactly that. Out of scope, filed as #5688: a `timeDimensions` entry carrying only a `dateRange` acquires the dataset's default granularity, turning a WINDOW into a second GROUP BY. Reproduces identically on the path this fix never touched; pinned here as data (row count included) so #5688 landing goes red on purpose. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 39e43c8 commit 8dbd2a8

4 files changed

Lines changed: 499 additions & 3 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@objectstack/service-analytics": patch
3+
---
4+
5+
fix(service-analytics): dataset 响应的 `fields` 在「度量全部自带 filter」的路径上也描述维度列 (#5537)
6+
7+
一个 dataset 查询,只要它的**基础度量全部带有自身的 `filter`**(或它选中的 derived
8+
度量的依赖全部如此),响应里的 `fields` 就只剩度量列,被选中的维度**完全没有描述符**
9+
维度值一直都在 `rows` 里(它就是合并键),但读取列元数据的消费者拿不到维度列的
10+
`label``type`,只能退回去 humanize 原始行键。
11+
12+
HotCRM「Sales Performance」上肉眼可见:同一个声明了 `label: 'Owner'``owner` 维度,
13+
"Open Pipeline by Owner"(度量无 filter)表头是 `Owner`,而 "Win / Loss by Rep"
14+
(`won_count`/`lost_count` 各带 filter、`win_rate` 是 ratio)表头是小写 `owner`
15+
换成字符串维度 `lead_source` 看起来正常纯属巧合 —— humanize 后恰好等于真 label;
16+
两种维度的描述符其实都丢了。
17+
18+
根因在网格装配处,不在渲染端:`DatasetExecutor.runMeasurePass` 只有在存在**无 filter**
19+
度量时才发那条主查询;当每个基础度量都自带 filter 时,它从 `{ rows: [], fields: [] }`
20+
起步,而随后每个补充子查询只追加一个**度量**描述符。现在这种情况下,维度描述符取自
21+
**第一个补充子查询自己的结果** —— 它 group by 的维度与整个网格完全一致 —— 因此两条路径
22+
`fields` 形状(维度在前、顺序、`type`)按构造收敛,而不是靠 executor 再抄一份
23+
「哪些维度被投影」的规则(该规则的单一事实源在各 strategy 的 `buildFieldMeta`,#4033)。
24+
25+
`compareTo``totals` 与 derived 度量都经由同一条 pass,所以一并修好。
26+
27+
已知的相邻缺口**不在**本次修复范围,单独立了 #5688:一个只带 `dateRange`
28+
`timeDimensions` 条目会被补上 dataset 的默认粒度,于是「窗口」变成第二层 GROUP BY,
29+
网格被按月拆分、并多出一个没人选过的时间列(该列在 `fields` 里也拿不到 `label`)。
30+
它在两条路径上表现一致(本次修复前后皆然),且修它会改变响应形状,故不搭车。

0 commit comments

Comments
 (0)