Skip to content

analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688

Description

@os-zhuang

立单于 #5537 的实施过程(PR 见该单)。与 #5537 的 fields 装配无关:下面的复现跑在 #5537 从未影响的那条「无 measure filter 单查询」路径上,且在 origin/main(c36abfe98,含 #5587/#5634/#5667)与 #5537 的分支上逐字节同样复现。范围外,单独立单。

现象

一个 dataset selection 只把日期维度用作窗口(timeDimensions: [{ dimension, dateRange }],不带 granularity,也不把它列进 selection.dimensions)——这正是 dashboard 日期区间筛选器的产物,executor 自己的注释就把它称作 "a dashboard date-range filter is the usual source"——结果网格却额外按该日期维度分桶:多出一列没人选过的时间列,行数按月裂开。

复现

dataset(关键条件:日期维度声明了显式 dateGranularity):

dimensions:
  - { name: owner,      field: owner_id,   type: lookup, label: Owner }
  - { name: close_date, field: close_date, type: date,   label: Close Date, dateGranularity: month }
measures:
  - { name: opp_count,  aggregate: count,  label: Opps }

三条数据:u1 @2026-01、u1 @2026-02、u2 @2026-01。

selection —— 只按 owner 分组,日期只当窗口:

{ dimensions: ['owner'],
  measures: ['opp_count'],
  timeDimensions: [{ dimension: 'close_date', dateRange: ['2026-01-01', '2026-02-28'] }] }

实测响应:

fields [{"name":"owner","type":"string","label":"Owner"},
        {"name":"close_date","type":"time"},          <-- 没人选过这一列
        {"name":"opp_count","type":"number","label":"Opps"}]
rows   [{"owner":"u1","close_date":"2026-01","opp_count":1},
        {"owner":"u1","close_date":"2026-02","opp_count":1},   <-- u1 被拆成两行
        {"owner":"u2","close_date":"2026-01","opp_count":1}]

期望:2 行(u1: 2、u2: 1),fields 为 owner + opp_count。

落点

packages/services/service-analytics/src/dataset-executor.ts buildQuery(origin/main c36abfe L888-L909):

const resolvedTimeDims = selTimeDims.map((t) => {
  if (t.granularity) return t;
  const granularity = granularityFor(t.dimension);   // L899
  return granularity ? { ...t, granularity } : t;
});

granularityFor 走 resolveDimensionGranularity(selection, name, datasetDefault),datasetDefault 即「dataset 显式声明过 dateGranularity」时编译出的单元素 cube.granularities。于是一个只有 dateRange 的条目被补上 granularity: 'month';而一旦条目带上 granularity,它就是 GROUP BY 项、并且按 #4033 被 projectedDimensions(objectql-strategy.ts L1130,native 侧同义)投影成结果列。

这条补默认值本身是刻意的,buildQuery 上方的长注释写明了理由:compareTo 需要的正是「窗口条目不得抑制分桶」,否则主网格按月、比较网格按原始时间戳,两边维度键对不上,每个 compare 列都空。所以两种需求在同一处打架:

判据看起来应该是「这个 dimension 是否出现在 selection.dimensions(或调用方自己写了 granularity)」,但这属于会改变响应形状的契约取舍,不该由我在 #5537 里顺手猜,故立单。

触发条件与影响面

必要条件(三者同时):dataset 的该日期维度声明了显式 dateGranularity;selection 的 timeDimensions 条目不带 granularity;selection.dateGranularity 未设。HotCRM 一类 dataset 普遍声明 dateGranularity,dashboard 的日期区间筛选器又普遍只发 dateRange,因此可达性不低。

命中时是行数与列集都变(不是纯元数据问题):一个「Won by Owner」表加上日期筛选后每人一行变成每人每月一行,KPI 单值卡会拿到多行里的第一行。

附带的次要症状(同一根因,同一条路径):这样投影出来的时间列在 fields 里只有 type 没有 label —— analytics-service.ts 的维度 label 富化(L915 起)只遍历 selection.dimensions,看不到只在 timeDimensions 里的列。#5537 的 PR 把这一点当作两条路径一致的既有行为钉住了(dataset-dimension-field-descriptors.test.ts 里成对的控制用例),并明确指向本单。

不重复

已搜:timeDimensions dateGranularity(命中 #3650/#3777,均已关闭且是另一件事:前者是 dateRange 被忽略,后者是上界打在 datetime 列上)、dataset-executor buildQuery grouping、dateRange granularity bucket dashboard widget rows、date range filter extra column group by month widget —— 无 open 重复。

Activity

  1. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    分诊:转 needs-user-decision,预挂 domain:services。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 6, 2026

    @claude
    Contributor

    处置(2026-08-06):退回派发队列。

    复核结论:这是恢复已确立不变量(「与 find() 给出相同的行集,或以 INVALID_FILTER 拒收——不允许第三种更安静的答案」),非产品分叉,不占决策箱。

    修法判据:timeDimensions 条目仅当 ∈ selection.dimensions、或调用方显式写了 granularity/selection.dateGranularity 时才回填粒度;纯窗口条目保持纯窗口(dateRange 是 filter,只减行、不改网格形状)。

    验收:窗口-only 条目不产生网格列/不裂行;被选中或显式粒度的条目保持分桶(钉住 #3588/#4870,compareTo 对齐不回归);compareTo 主/比较趟成对用例;顺手收掉 #5691 钉住的 label 富化盲区。量级 M。

    ⚠️ 派发排程:与 #5739 同落 service-analytics,不同轮派发。

    经办:PM 会话 session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。


    Generated by Claude Code

  3. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:

    裁「回填判据收窄」——仅当该 timeDimension 同时出现在 selection.dimensions、或作者显式给了 granularity 时才回填默认 dateGranularity;纯日期窗口用途不再被静默按月拆分。

    同场必答项:compareTo 在窗口-only 情形的对齐语义须在同一实现中定义并测试——回填本是 #3588/#4870 的刻意修复,两个已立案需求在同一处相反,必须写成对用例钉住双向行为,防翻烧饼回归。

    流转:摘 needs-user-decision → pm:queue,归 services 车道。

    评估与落地会话:session_01N3uGFF8teXbpgtbEJ1aYXu


    Generated by Claude Code

  4. hotlong commented on Aug 6, 2026

    @hotlong
    Contributor

    排程注记(services 座位,第 2 轮选批时记录 —— 延后不是搁置,已知坑先写在单上):

    1. 同文件串行:本单落点 dataset-executor.ts 的 buildQuery / resolveDimensionGranularity,与 analytics dataset 路由:另有九处「作者/调用方形状」的 dataset 拒收仍答 500 —— 它们从来没进过 #5352 的正则名单,所以 #5367 的信封化也没覆盖到 #5716(九处拒收信封化,其中四处在同一文件 :466-532)同文件。analytics dataset 路由:另有九处「作者/调用方形状」的 dataset 拒收仍答 500 —— 它们从来没进过 #5352 的正则名单,所以 #5367 的信封化也没覆盖到 #5716 已于第 2 轮派发,本单严格后轮(预计 R3),开工前基于含 analytics dataset 路由:另有九处「作者/调用方形状」的 dataset 拒收仍答 500 —— 它们从来没进过 #5352 的正则名单,所以 #5367 的信封化也没覆盖到 #5716 的合并后 main。
    2. 翻 pin 义务:[17.0-rc2验收] analytics: 带 measure-scoped filter / derived 度量的 dataset 查询,响应 fields 丢失维度描述符 → 表头回退成原始维度名(如 "owner" 而非 "Owner") #5537 的 PR 已把「两条路径一致的既有行为」用成对控制用例钉住并明确指向本单(dataset-dimension-field-descriptors.test.ts)—— 本单落地时那组 pin 是要翻的靶,翻后必须承重(断言窗口型 timeDimension 不再分桶的实质),不是删断言。
    3. compareTo 约束:buildQuery 上方长注释记录了补默认粒度的存在理由(compareTo 需要「窗口条目不得抑制分桶」)—— 修法判据要同时保住 compareTo 对齐路径,analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688 正文已述。
    4. 关联提示:fix(service-analytics): 即席推断的 Cube 把 owner.region 当成关系穿越,不再铸成基表列 region (#5739) #5923(analytics 自动推断路径:inferCubeFromQuery 的 stripPrefix 把关系穿越 owner.region 铸成基表列 region —— 基表恰好有同名列时静默筛/分组错列(两个策略、两个请求键均如此) #5739)只改了即席 cube 铸造时 timeDimension 的键,对本单「完全无影响」(dev 必答项已核);analytics dataset 路由:另有九处「作者/调用方形状」的 dataset 拒收仍答 500 —— 它们从来没进过 #5352 的正则名单,所以 #5367 的信封化也没覆盖到 #5716 的 dev 将回答其改动对本单的影响,派发本单前以其回答重定价。

    Generated by Claude Code

  5. self-assigned this
    on Aug 6, 2026
  6. hotlong commented on Aug 6, 2026

    @hotlong
    Contributor

    认领:PM 循环第 3 轮补入席(services 车道)
    会话:session_015a5qkLzpGXhLL2F5gvJ7dD
    分支:claude/issue-5688-window-timedim-no-bucket
    Worktree:objectstack-issue-5688
    域:domain:services
    文件面:packages/services/service-analytics/src/dataset-executor.ts(buildQuery / resolveDimensionGranularity)+ analytics-service.ts 仅限 label 富化盲区若属同根因(:915 一带,按裁决范围内判)+ dataset-dimension-field-descriptors.test.ts(#5537 成对控制用例 = 翻转靶)+ 同包测试;⛔ 不触本文件今日 #5963 刚信封化的四处 throw(其测试须保持绿)
    串行约束已清:前序 #5716→PR #5963 已 MERGED(其必答项答「完全无影响」—— 重定价:成本模型不变);同包无其它在飞;基于合并后 origin/main(今日 fb3d99b/#5963 均触过本文件,正文行号已过期按内容定位)

    执行口径:排程注记(13:0xZ 本单评论)四条照旧;裁决与前提随派发令发出。


    Generated by Claude Code

  7. hotlong commented on Aug 6, 2026

    @hotlong
    Contributor

    验收:ACCEPT → PR #6003(CI 全绿,随后转 ready 入队,本座位跟到 MERGED)。

    经办:services 座位,会话 session_015a5qkLzpGXhLL2F5gvJ7dD(第 3 轮补入席)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions