Repository navigation
Commit 8d36669
feat(spec): a report chart can split its bars by a second dataset dimension (
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoXKA5bTPC7aHkxyxEdEEiFv)_
Fixes #27974
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #28207
(skills/objectstack-ui/rules/dashboards.md, to land with the renderer
half #28206)
## 维护者速读
- 改了什么:报表图表(`report.chart`)新增一个可选键
`seriesBy`,填数据集的第二个维度名,把同一个度量按这个维度的每个取值拆成多组柱子,比如"每个负责人一根柱,按阶段分组"。
- 为什么改:报表图表以前只有一个 X 维度和一个 Y 度量,画不出"按 X 再按 Y 拆分"的图;HotCRM
的商机漏斗按负责人×阶段只能退回成矩阵表。仪表盘早就能这样画,报表缺这一个键。
- 风险与回滚:只加了一个可选键,不填的报表解析和绘制完全不变;拼错的维度名在 `os validate` / 发布时报错。回滚即 revert
本 PR。本 PR 只是契约这一半,图表还不会画出拆分,作者写了会收到"已声明、尚未生效"的提示,渲染器那一半是后续卡。
- 先看:`packages/spec/src/ui/report.zod.ts` 的
`ReportChartSchema`、`packages/lint/src/validate-chart-bindings.ts` 的
`checkReportSurface` 图表分支、`packages/spec/liveness/report.json` 的
`chart.children.seriesBy`。
- 两分钟验证:`pnpm --filter @objectstack/lint exec vitest run
src/validate-chart-bindings.test.ts -t seriesBy`,预期 2 条全绿(拼错的 `seriesBy`
报 `chart-dimension-unknown` 并提示最近的维度名)。
- 需要你做的:无,席位落地。
## What changes
A report chart could plot one `xAxis` dimension against one `yAxis`
measure, so a bar per owner split by stage was not expressible; HotCRM's
`opportunity_funnel_owner_stage` fell back to an owner × stage `matrix`
(hotcrm#2379). This is the spec half of the card: the key, its
publish-time check, and its liveness grade.
- **Spec (`packages/spec/src/ui/report.zod.ts`).** `ReportChartSchema`
declares one optional key, `seriesBy`: a second dimension name of the
report's bound dataset. It splits the chart's one `yAxis` measure into a
series per value of that dimension. It is the report's spelling of a
dashboard widget's two-entry `dimensions` selection (`[xAxis, seriesBy]`
× `[yAxis]`), which the dataset path already pivots into grouped series
(`buildChartSeries` in `ui/packages/core`). With `seriesBy` set, a
`chart.series[]` entry's `name` names a VALUE of that dimension, because
each derived series is one value. The docblock and the `.describe()` say
both.
- **Lint (`packages/lint/src/validate-chart-bindings.ts`,
`validateChartBindings`).** This is the check that resolves
`chart.xAxis` against the dataset's declared dimensions.
`chart.seriesBy` now goes through the same `dimensionRef`, so an
undeclared name is a `chart-dimension-unknown` error at `os validate`,
`os build` and the report publish door, with the nearest declared
dimension suggested. When `seriesBy` is set, the `chart.series[].name`
overrides are not judged against the chart's measure, since they name
dimension values that no static check can see. The `os explain
chart-axis-not-selected` text (`rule-explanations.ts`) says so.
- **Liveness (`packages/spec/liveness/report.json`).** The `chart` row
now drills: `childrenDefault: live` keeps every existing chart key on
the row's verdict, `seriesBy` is graded `planned` with `authorWarn`, so
an author who writes it is told nothing draws it yet, and the tombstoned
`aria` child, now walked, is graded `dead`. Drilling surfaced the three
nested containers under `chart` (`series`, `annotations`,
`interaction`); they are recorded in
`undrilled-containers.baseline.json` in place of the single
`report/chart` row they used to ride under, with no per-key evidence
invented. `state-counts/report.md` regenerated.
- **Generated.** `content/docs/references/ui/report.mdx` (`gen:docs`)
and `authorable-surface/ui.json` (one `ui/ReportChart:seriesBy` line).
- **Changeset.** `.changeset/27974-report-chart-series-by.md`:
`@objectstack/spec` minor, `@objectstack/lint` minor.
### The shape chosen, and the alternatives not taken
- **A new `seriesBy` key, not the inherited `series`.**
`ChartConfigSchema.series` already exists on the report chart as the
per-series presentation override array (`{ name, label, color, stack, …
}`); reusing that name for a dimension would give one key two meanings.
`ChartConfigSchema` declares no dimension-valued series member to reuse.
- **Not `groupBy`.** On the react `ObjectChart` block's `aggregate`
`groupBy` is the X-axis category, and on kanban views it is the lane
field, so a report author reading `groupBy` would expect the X axis.
- **Not widening `xAxis` to a list.** That changes the type of a key
every renderer and the Studio inspector read as one string.
- **Not reusing the report's own `columns`.** `columns` is the matrix
table's across axis; tying the chart's split to the table would stop a
summary report from splitting its chart.
## Tests
- `packages/spec/src/ui/report.test.ts`: `seriesBy` is accepted and kept
as authored, absent stays `undefined`, and a list value is refused at
`seriesBy`.
- `packages/spec/src/ui/chart.test.ts`: the `.extend()` strictness pin
accepts `seriesBy` as the report chart's own key and still refuses an
undeclared one with the inherited rename.
- `packages/lint/src/validate-chart-bindings.test.ts`: a misspelled
`seriesBy` is one `chart-dimension-unknown` error at
`reports[0].chart.seriesBy` with a did-you-mean, against a
declared-dimension control; a `series[].name` naming a value warns
without `seriesBy` (control) and is silent with it.
## Acceptance notes
- **The renderer half is a follow-up `domain:ui` card** (the seat files
it when this lands): `DatasetReportChart` in
`ui/packages/plugin-report/src/DatasetReportRenderer.tsx` queries
`[xAxis]` × `[yAxis]` and passes one dimension to `buildChartSeries`. It
needs to add `seriesBy` to the query dimensions and pass `[xAxis,
seriesBy]`, match `chart.series[].name` overrides against the pivoted
series keys, and flip the `chart.seriesBy` liveness row to `live`. Until
then the key is graded `planned` and authors are warned.
- **No rename aliases for `seriesBy`.** The report chart's unknown-key
answer is `ChartConfigSchema`'s error map, which rides `.extend()`. A
report-only alias such as `groupBy → seriesBy` would have to live on
that shared base, where it is wrong for the inline-data tier.
- **`seriesBy` equal to `xAxis`** parses and lints clean. It draws one
series per bar, which is harmless; a refusal can follow if it turns up
in practice.
- **Stacking** stays a series property (`series[].stack`). With
`seriesBy` set, stacking the split means naming each value with a shared
`stack` group; a chart-level switch is not part of this card.
## Verification
All readings at head `061a780d` on objectstack-ai/objectstack (merge
base `9f13ee81`).
<details><summary>Tests</summary>
- `pnpm --filter @objectstack/spec exec vitest run src/ui
scripts/liveness`: Test Files 176/176, Tests 6227/6227.
- `pnpm --filter @objectstack/lint test`: Test Files 178/178, Tests
7646/7646.
- `pnpm --filter @objectstack/spec typecheck` and `pnpm --filter
@objectstack/lint typecheck`: lock VERDICT command-exit 0.
- Ablations through `scripts/ablation-replace.mjs`, each restored to the
HEAD blob: (1) the `seriesBy` `dimensionRef` line deleted, lint suite 1
failed / 80; (2) the `seriesBy ? []` series guard reverted, 1 failed /
80.
- Author warning probe (`lintLivenessProperties` on a report with and
without `chart.seriesBy`): one `liveness-planned-property` finding, on
the report that sets it.
</details>
<details><summary>Gates</summary>
- `dispatch-gates --commands` at `061a780d`: 106 families, each run with
stdin from `/dev/null`, exit codes recorded before any pipe. `--ran`:
106 derived, 106 run, 0 NOT MEASURED, 0 unrun, 0 red.
- Four families first answered exit 3 (prerequisite not met) and were
re-run after their prerequisite, all exit 0:
`check-plugin-teardown-shape --self-test` (after fetching the pinned
control commit into the shallow clone), spec `check:skill-examples`,
`check:dual-build-cjs-loads` and `check:lean-entry-closure` (after a
full `packages/**` build).
- `pnpm --filter @objectstack/spec check:generated`: all 14 generated
artifacts up to date. `check:liveness`: exit 0.
- The full CI farm (`test:repo`, the UI lanes) is CI's.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibxKEFRdVkHHwAWLUSE)_
---------
Co-authored-by: Claude <noreply@anthropic.com>chart.seriesBy) (#28191)1 parent 3a2f4c0 commit 8d36669
12 files changed
Lines changed: 135 additions & 8 deletions
File tree
- .changeset
- content/docs/references/ui
- packages
- lint/src
- spec
- authorable-surface
- liveness
- state-counts
- scripts/liveness
- src/ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
| 107 | + | |
107 | 108 | | |
108 | 109 | | |
109 | 110 | | |
| |||
167 | 168 | | |
168 | 169 | | |
169 | 170 | | |
| 171 | + | |
170 | 172 | | |
171 | 173 | | |
172 | 174 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3306 | 3306 | | |
3307 | 3307 | | |
3308 | 3308 | | |
3309 | | - | |
| 3309 | + | |
| 3310 | + | |
| 3311 | + | |
3310 | 3312 | | |
3311 | 3313 | | |
3312 | 3314 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
131 | 174 | | |
132 | 175 | | |
133 | 176 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | 26 | | |
| |||
269 | 271 | | |
270 | 272 | | |
271 | 273 | | |
| 274 | + | |
| 275 | + | |
272 | 276 | | |
273 | 277 | | |
274 | 278 | | |
| |||
501 | 505 | | |
502 | 506 | | |
503 | 507 | | |
| 508 | + | |
504 | 509 | | |
505 | 510 | | |
506 | 511 | | |
| |||
576 | 581 | | |
577 | 582 | | |
578 | 583 | | |
| 584 | + | |
579 | 585 | | |
580 | 586 | | |
581 | 587 | | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
582 | 591 | | |
583 | 592 | | |
584 | 593 | | |
585 | 594 | | |
586 | 595 | | |
587 | | - | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
588 | 600 | | |
589 | 601 | | |
590 | 602 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1260 | 1260 | | |
1261 | 1261 | | |
1262 | 1262 | | |
| 1263 | + | |
1263 | 1264 | | |
1264 | 1265 | | |
1265 | 1266 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
55 | 72 | | |
56 | 73 | | |
57 | 74 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
122 | | - | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
123 | 125 | | |
124 | 126 | | |
125 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
370 | 370 | | |
371 | 371 | | |
372 | 372 | | |
373 | | - | |
374 | | - | |
375 | | - | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
376 | 377 | | |
| 378 | + | |
377 | 379 | | |
378 | 380 | | |
379 | 381 | | |
| |||
0 commit comments