Skip to content

[17.0-rc2验收] analytics: 带 measure-scoped filter / derived 度量的 dataset 查询,响应 fields 丢失维度描述符 → 表头回退成原始维度名(如 "owner" 而非 "Owner") #5537

Description

@yinlianghui

现象

当一个 dataset 查询的 measures 里包含带自身 filter 的度量(measure-scoped filter 子查询合并路径)或 derived 度量 时,POST /api/v1/analytics/dataset/query 返回的 fields 数组会完全省略维度(dimension)的描述符。维度值仍在 rows 里,但没有对应的 {name,type,label,format} 条目。于是任何消费者(console 表格渲染器)拿不到维度列的 label,只能回退到原始行键。

HotCRM 「Sales Performance」仪表盘上肉眼可见:同一个 owner 维度(dataset 声明 label:'Owner'),

  • Open Pipeline by Owner(度量无 filter)表头渲染为 Owner
  • Win / Loss by Rep(度量 won_count/lost_count/… 各带 filter + win_rate 是 ratio)表头渲染为小写 owner

而同一张 Win/Loss 表若把维度换成字符串维度 lead_source,表头显示 Lead Source —— 这是巧合:console 的回退是「按原始键 humanize」,lead_source humanize 恰好得到 Lead Source(与真 label 相同),owner humanize 得到不变的 owner(≠ label Owner),于是 owner 把这个 bug 暴露了出来。实际上两种维度的 label/format/type 元数据都丢了,只是字符串维度被 humanize 掩盖。

复现(curl,可直接执行)

BASE=http://localhost:4099
TOKEN=$(curl -s -X POST $BASE/api/v1/auth/sign-in/email -H 'Content-Type: application/json' \
  -d '{"email":"admin@objectos.ai","password":"admin123"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])')

# A) 无 filter 度量 —— fields 含维度 owner ✅
curl -s -X POST $BASE/api/v1/analytics/dataset/query -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"datasetName":"opportunity_metrics","selection":{"dimensions":["owner"],"measures":["opp_count"]}}' \
  | python3 -c 'import sys,json;print([f["name"] for f in json.load(sys.stdin)["fields"]])'
# => ['owner', 'opp_count']

# B) 带 filter 的度量 —— fields 丢了维度 owner ❌
curl -s -X POST $BASE/api/v1/analytics/dataset/query -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"datasetName":"opportunity_metrics","selection":{"dimensions":["owner"],"measures":["won_count"]}}' \
  | python3 -c 'import sys,json;print([f["name"] for f in json.load(sys.stdin)["fields"]])'
# => ['won_count']            ← 维度 owner 不在 fields 里,但 rows 里有 owner

# C) 字符串维度同样丢失(被 humanize 掩盖) ❌
curl -s -X POST $BASE/api/v1/analytics/dataset/query -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"datasetName":"opportunity_metrics","selection":{"dimensions":["lead_source"],"measures":["won_count","decided_count","win_rate"]}}' \
  | python3 -c 'import sys,json;d=json.load(sys.stdin);print("fields",[f["name"] for f in d["fields"]],"rowkeys",list(d["rows"][0].keys()))'
# => fields ['won_count','decided_count','win_rate']  rowkeys ['lead_source','won_count','decided_count','win_rate']

复现两次稳定。浏览器侧(chromium + storageState 登录)抓 sales_dashboard 五张 table 的 <thead>,两次运行都得到:Open Pipeline by Owner → Owner;Quota Attainment by Rep → Owner(forecast_metrics);Win / Loss by Rep → owner;Win / Loss by Lead Source → Lead Source;pivot → Stage

期望 vs 实际

  • 期望:无论度量是否带 filter / 是否 derived,响应 fields 都应包含被选维度的描述符(name/type/label/format),表头恒显示 Owner
  • 实际:一旦查询走了「measure-scoped filter 子查询合并」或含 derived 度量的路径,fields 只剩度量列,维度描述符缺失;维度列的 label/format/type 全部丢失,渲染器回退到原始键。

落点分析

packages/services/service-analytics/src/analytics-service.ts queryDataset 尾部:

  • L873–907 把度量列的 label/format/currency/percentScale 富化到 result.fields(遍历已存在的 field 条目)。
  • L909–923 有一段专为维度补 label 的代码,注释即写着 “the measure-only enrichment above left dimension headers bare (the renderer then fell back to the raw dimension name)” —— 但它 for (const f of result.fields) 只富化已经存在于 result.fields 的维度条目,不会为缺失的维度新建 field 条目。

而在带 filter/derived 度量的路径上,度量来自 dataset-executor.ts 的补充分组子查询,经 mergeByDimensions 合并回主网格(见该文件 L28、L140–194 的 mergeByDimensions/partitionMeasures)。合并出的 result.fields根本没有维度列(rows 里有,fields 里没有),于是 L909–923 的富化对维度是空操作,原始键泄漏到前端。

建议修复方向(任一):在 executor 的合并路径上把被选维度的 field 描述符补进 result.fields;或把 L909–923 改成「维度缺失时新建 field 条目」而非仅富化已存在条目。console 端的 humanize 回退是次要症状,根因在服务端 fields 装配。

影响面

  • 前端:凡使用带 filter/derived 度量的 dataset table(win-rate 家族、任何 measure-level filter 的看板)维度列表头显示原始键;数值维度会额外丢失 format(如日期维度的 dateGranularity 格式、货币/百分比 scale)。
  • 数据正确、行数正确,仅列元数据丢失 → 归为 p2(体验/一致性,非数据错误)。

HotCRM 关联(仅记录,不在 hotcrm 重复立单):sales_dashboard 「Win / Loss by Rep」表头小写 owner。HotCRM 元数据 opportunity.dataset.ts 声明 { name:'owner', label:'Owner', field:'owner_id', type:'lookup' } 正确,问题在平台。

环境

hotcrm@0899b4f + @objectstack 17.0.0-rc.2;独立 dev server(file DB, --seed-admin, admin@objectos.ai)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions