Repository navigation
Commit 3ba5206
feat(plugin-report): a summary report with two or more row levels draws per-group subtotals (#28376)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao2NFfgMD4j4LmnpdPFFKbkT)_
Fixes #27975
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:`summary` 报表的 `rows`
有两层及以上时,按外层分组画:每组一行组头(外层值只写一次)、组内明细行(外层列留空)、一行「小计」;三层时逐层嵌套。小计取服务端按前缀分组算出的值,浏览器不做加总。
- 为什么改:之前多层汇总报表只要总计,外层值在每行重复、没有分组小计(如 HotCRM「按负责人和阶段的商机漏斗」)。triage
6539817671 裁定为默认行为、不新增键。
- 风险与回滚:所有已有的两层以上 `summary` 报表下次渲染即出现小计行(已在 changeset 写明);单层
summary、`tabular`、`matrix` 不变;服务端不回前缀分组时退回原平铺表。回滚即 revert 本 PR。
- 先看:`ui/packages/plugin-report/src/DatasetReportRenderer.tsx` 的
`DatasetReportTable`(totals 请求与 `lines` 渲染)和 `nestSummaryLines` /
`spanDrillRanges`;新测试
`DatasetReportRenderer.summarySubtotals-27975.test.tsx`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/plugin-report/src/__tests__/DatasetReportRenderer.summarySubtotals-27975.test.tsx`,预期
16 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
**Renderer (`ui/packages/plugin-report/src/DatasetReportRenderer.tsx`,
`DatasetReportTable`).**
- A `summary` table with two or more `rows` levels asks the dataset for
every proper prefix of `rows` plus the grand total through
`totals.groupings`: `[[a], []]` for `[a, b]`, `[[a], [a, b], []]` for
`[a, b, c]` (`summarySubtotalPrefixes`). The server already re-runs the
selection per grouping, which is what the `matrix` path relies on.
- When every prefix grouping is answered, the body is drawn as nested
groups (`nestSummaryLines`): per outer group a header row naming the
value once, its inner lines (the outer columns left blank), and a
`Subtotal` row carrying the server's pre-aggregated row for that prefix.
The subtotal lookup relabels the totals rows the same way the display
rows are relabeled and matches by bucket id, the matrix's rule. Nothing
is summed client-side.
- Groups keep the order of their first row in the laid-out rows, so the
declared-option layout and an authored `order` both hold; rows of one
group that an `order` by a measure returns apart are drawn together
under it.
- Drill: an inner row drills by every dimension, as before. A subtotal
row drills by the prefix only: `groupKey` and the raw-value filter take
the prefix dimensions, and its ranges are the span of its group's ranges
(`spanDrillRanges`): a prefix range passes through, an inner time bucket
widens to the group's buckets (already narrowed to any report period by
the server), and a key some row lacks (the empty bucket) is dropped.
- The export snapshot writes the grouped table exactly as drawn.
- Unchanged faces: a one-level `summary` (still `[[]]`, grand-total
footer only), `tabular` (no totals), a `matrix` with `columns`, and a
`matrix` without `columns` (it still degrades to this table with the
grand total only; `withSubtotals` is passed for `summary` alone, top
level and joined blocks). An answer missing any prefix grouping (an
older server) draws the plain table with the outer value repeated.
**i18n.** `report.subtotal` added to all ten packs, reusing each pack's
existing `masterDetail.subtotal` wording.
**Changeset.** `.changeset/27975-summary-report-subtotals.md`, patch for
`@object-ui/plugin-report` and `@object-ui/i18n`, with both declaration
lines and the visible default change named.
## Tests
- New
`ui/packages/plugin-report/src/__tests__/DatasetReportRenderer.summarySubtotals-27975.test.tsx`,
16 cases: the totals request for two and three levels; the grouped body
for two levels; the server subtotal placed where the rows would sum
differently (an `avg`); three-level nesting; grouping under an
interleaving authored `order`; a joined summary block; drill on a
subtotal (prefix only), on an inner row (every dimension), over a
time-bucketed inner dimension (span), and over a group holding the empty
bucket (range dropped). Controls: older server without the prefix
grouping, no totals at all, one-level summary, two-level `tabular`,
`matrix` without `columns`.
- `DatasetReportRenderer.export-25873.test.tsx`: one new case, the CSV
of a two-level summary equals the drawn table cell for cell.
- Ablation (one-off, no permanent file): `node
scripts/ablation-replace.mjs` replaced `withTotals && withSubtotals ?
summarySubtotalPrefixes(rows) : []` with an empty list (anchor 1 → 0,
blob `dc35dc45` → `4b92969b`). Predicted: every grouped-face case red,
the controls green. Measured over the two files: 11 failed, 18 passed.
Restore verified by the tool: blob back to `dc35dc45`, `git diff HEAD`
empty.
## Acceptance notes
- **Live measure (showcase, fresh stack, head before merge
`ddbba1e8`).** The showcase ships no two-level summary, so one was saved
into the throwaway `--fresh` database through `PUT
/api/v1/meta/report/showcase_hours_by_status_priority` (`rows: [status,
priority]`, `values: [est_hours]`); no `examples/**` edit. The Console
dev server rendered it from this branch's source.
- Before (the `origin/main` renderer swapped in, then restored
byte-identical): request `totals.groupings: [[]]`; 8 rows, each
repeating its status (`Backlog · Low · 12.0`, `Backlog · Medium · 18.0`,
…), footer `Total 244.0`.
- After: request `totals.groupings: [["status"], []]`; the server
answered `totals[0].dimensions: ["status"]` with 5 rows (Backlog 30,
Done 24, In Progress 100, In Review 54, To Do 36) and `totals[1]` the
grand total 244. Drawn: `Backlog` header, `Low 12.0`, `Medium 18.0`,
`Subtotal 30.0`, … `Done` header, `Medium 24.0`, `Subtotal 24.0`, `Total
244.0`, in the status field's declared order.
- Clicking the Backlog subtotal opened the drill drawer titled `Backlog`
with both Backlog tasks (Low and Medium).
- **Groups under an `order` by a measure** are ordered by their first
row, not by their subtotal. Ordering groups by subtotal would need the
server to order the prefix grouping, which it drops for totals by
design. Noted, not filed.
- **The subtotal label is the bare word `Subtotal`**
(`report.subtotal`), placed in the column of the level it closes; it
does not repeat the group's value. Noted.
- **Assignee and reviewer are not set on this PR.** The dispatch frame
bars the dev from writing them; the seat owns that.
- **Serial constraint with #28305** (same file, chart path): a local
`git merge-tree` of this branch against #28305's head `39eb423b` is
clean. #28305 is still open; this branch merges `origin/main` again
after it lands.
## Verification
All readings on head `91ccb5a5` (after merging `origin/main`) unless
named.
<details><summary>Affected package tests and type-checks</summary>
- `pnpm --dir ui exec vitest run packages/plugin-report/ packages/i18n/`
under `os-verify-lock`: Test Files 124 passed (124), Tests 1728 passed,
20 skipped. VERDICT command-exit 0.
- `pnpm --filter @object-ui/plugin-report --filter @object-ui/i18n run
type-check` (after building the plugin-report dependency closure with
declarations): VERDICT command-exit 0.
- eslint over the three changed source/test files: 0 errors; the 5
warnings are on lines this diff does not touch.
- `pnpm check:new-line-citations` (ui): 0 new citations.
</details>
<details><summary>Derived gates (dispatch-gates --commands, reconciled
with --ran)</summary>
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--ran` on head `ddbba1e8`: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN;
every recorded exit code 0. `node scripts/check-sdui-manifest.mjs` and
its `--self-test`: exit 0. `pnpm --dir ui exec vitest run scripts/`:
Test Files 169 passed, 2 skipped; Tests 4901 passed, 2 skipped.
- After the `origin/main` merge the derivation on `91ccb5a5` names the
same 34 families. Re-run there: 32 of them, all exit 0. Not re-run on
`91ccb5a5`: `pnpm check:type-check-debt` and `pnpm --dir ui exec vitest
run scripts/` (both green on `ddbba1e8`; the merge brought no change to
`ui/scripts/` or to a type-check ledger).
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibwTmv3iyLca6doQ7h7)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0ac6dab commit 3ba5206
14 files changed
Lines changed: 646 additions & 32 deletions
File tree
- .changeset
- ui/packages
- i18n/src/locales
- plugin-report/src
- __tests__
| 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3574 | 3574 | | |
3575 | 3575 | | |
3576 | 3576 | | |
| 3577 | + | |
3577 | 3578 | | |
3578 | 3579 | | |
3579 | 3580 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3309 | 3309 | | |
3310 | 3310 | | |
3311 | 3311 | | |
| 3312 | + | |
3312 | 3313 | | |
3313 | 3314 | | |
3314 | 3315 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1782 | 1782 | | |
1783 | 1783 | | |
1784 | 1784 | | |
| 1785 | + | |
1785 | 1786 | | |
1786 | 1787 | | |
1787 | 1788 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3376 | 3376 | | |
3377 | 3377 | | |
3378 | 3378 | | |
| 3379 | + | |
3379 | 3380 | | |
3380 | 3381 | | |
3381 | 3382 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3372 | 3372 | | |
3373 | 3373 | | |
3374 | 3374 | | |
| 3375 | + | |
3375 | 3376 | | |
3376 | 3377 | | |
3377 | 3378 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3311 | 3311 | | |
3312 | 3312 | | |
3313 | 3313 | | |
| 3314 | + | |
3314 | 3315 | | |
3315 | 3316 | | |
3316 | 3317 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3308 | 3308 | | |
3309 | 3309 | | |
3310 | 3310 | | |
| 3311 | + | |
3311 | 3312 | | |
3312 | 3313 | | |
3313 | 3314 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3371 | 3371 | | |
3372 | 3372 | | |
3373 | 3373 | | |
| 3374 | + | |
3374 | 3375 | | |
3375 | 3376 | | |
3376 | 3377 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3452 | 3452 | | |
3453 | 3453 | | |
3454 | 3454 | | |
| 3455 | + | |
3455 | 3456 | | |
3456 | 3457 | | |
3457 | 3458 | | |
| |||
0 commit comments