Repository navigation
Commit 606c32d
fix(plugin-report): a report draws a dataset dimension nullLabel for its null bucket (#29334)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbNoZJxAjLuNF5LVf8GtR5wQ)_
Fixes #29010
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:数据集维度声明了 `nullLabel`(如 "No region")时,报表里该维度的空值不再显示
"—",而是显示这个名字。覆盖汇总表的行和分组标题、矩阵的行标题和列标题、报表内嵌图表的空值类目,以及 CSV/XLSX
导出,导出与屏幕上的字一致。没声明 `nullLabel` 的维度照旧显示 "—"。
- **为什么改**:同一个数据集在仪表盘上读作 "No region",在报表里却是 "—",读报表的人分不清"空值"和"缺数据";spec
原文要求渲染器用这个名字画空值,仪表盘早已照做,报表没有。
- **风险与回滚**:低。只改 `plugin-report`
一个文件的显示文字;分组、合计、钻取仍读原始值,非空值(包括空字符串)显示不变。回滚即 revert 本 PR。
- **先看**:① `ui/packages/plugin-report/src/DatasetReportRenderer.tsx` 新增的
`dimensionText` 及其六处调用;② 同文件的
`declaredNullCategoryLabel`(图表空值类目,照抄仪表盘规则:透视图两个维度声明同一个名字才用);③ 新测试
`DatasetReportRenderer.nullLabel-29010.test.tsx`。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/plugin-report/src/__tests__/DatasetReportRenderer.nullLabel-29010.test.tsx`,预期
8 个测试全绿(空值行、分组标题、矩阵行/列标题、CSV、图表类目都读 "No region (R8)",未声明的维度仍是 "—")。
- **需要你做的**:无,席位落地。
## What changes
A dataset dimension can name its null bucket with `nullLabel`, and the
dataset query answer carries it on the column descriptor,
`fields[].nullLabel`, already resolved to the request locale. The spec
(`packages/spec/src/api/analytics.zod.ts`, `nullLabel`) says: "A
renderer draws the column's null values under it instead of its own
default." The dashboard widget did (`DatasetWidget.tsx`, `nullLabelOf`);
the report drew `formatDimensionValue`'s "—" everywhere.
`ui/packages/plugin-report/src/DatasetReportRenderer.tsx` only:
- **`dimensionText(fields, dimension, value, locale)`** (module-local):
a null value reads as the column's declared `nullLabel` when the answer
carries one; otherwise, and for every non-null value, it is
`formatDimensionValue(value, locale)` unchanged (an empty string stays
an empty string). It reads the label off the answer the report already
holds (`state.fields`), the way the dashboard widget does; nothing is
resolved from metadata a second time, and `@object-ui/core`'s
`formatDimensionValue` is untouched.
- It replaces `formatDimensionValue` at every site the report draws a
dimension value: the summary table's detail cells and their export
cells, the grouped summary's group headers and their export cells, the
matrix row headers and their export cells, and `bucketLabel` (the matrix
row/column header labels, so the across-axis headers and their export
header cells too).
- **The report's embedded chart** (an extra site, agreed with the seat
in the card thread): its null category was `tt('chart.nullCategory',
'(None)')`. It now takes `declaredNullCategoryLabel(state.fields,
queryDimensions, [yAxis])` first, a plugin-report-local copy of the
dashboard widget's rule: the x-axis dimension's label, and on a
`seriesBy` pivot only when both dimensions declare the same one;
otherwise "(None)" as before. The same resolved label keeps the empty
series last in the series ordering.
- Bucket ids, cell lookups, drill payloads and grouping read the raw
value as before; this is display copy only.
## Tests
New
`ui/packages/plugin-report/src/__tests__/DatasetReportRenderer.nullLabel-29010.test.tsx`
(8 tests, real `DatasetReportRenderer` over a `queryDataset` double
whose answer declares `nullLabel` on `region` and none on `kind`):
- summary: the null row reads the label on screen, and the CSV equals
the drawn table;
- control: a column without `nullLabel` keeps "—", and an empty-string
value stays blank, on screen and in the CSV;
- grouped summary: the null group header reads the label (the inner
undeclared null keeps "—"), CSV equals the drawn table;
- matrix: the null row header reads the label while an undeclared
across-axis null header keeps "—", CSV equal;
- matrix: the null across-axis header reads the label, CSV header equal;
- chart: the x-axis null category reads the label; control: an
undeclared x-axis keeps "(None)"; a `seriesBy` pivot takes the label
only when both dimensions declare the same one.
Ablations (subject imported from `src` by relative path, so no build
leg; each run under `os-verify-lock`, restored and proved by blob hash
equal to `HEAD` and an empty `git diff HEAD`):
- the whole renderer reverted to `origin/main` (blob `cd8213a1`,
`dimensionText(` count 0) at `481cc948`: 4 red, the "—" control green;
- the chart site only, through `scripts/ablation-replace.mjs` (anchor 1
to 0, blob `0b68fe40` to `b77c6c08`) at `345a95fb`: 2 red (x-axis label,
pivot), the other 6 green.
## Acceptance notes
- **Measured on a real backend** (`examples/app-crm` under `objectstack
dev --seed-admin --fresh`, the Vite source Console proxying to it,
Chromium). Scratch package `app.qar8` with object `qar8_dsobj`, dataset
`qar8_dataset` (`region` with `nullLabel: 'No region (R8)'`, `kind`
without), reports `qar8_report` (summary), `qar8_matrix` (rows region,
columns kind), `qar8_matrix_across` (rows kind, columns region) and
`qar8_chart` (summary with a bar chart on region); rows seeded with
region null. The page's own query answer carried
`fields.region.nullLabel = "No region (R8)"`.
| report | before (origin/main) | after (this branch) |
|---|---|---|
| `qar8_report` screen / CSV | `— \| 2 \| 37` / `—,2,37` | `No region
(R8) \| 2 \| 37` / `No region (R8),2,37` |
| `qar8_matrix` row header / CSV | `—` / `—,30,7,—,37` | `No region
(R8)` / `No region (R8),30,7,—,37` (the `kind` null column header stays
`—`) |
| `qar8_matrix_across` column header / CSV header |
`Kind,—,North,South,Total` | `Kind,No region (R8),North,South,Total`
(the `kind` null row header stays `—`) |
| `qar8_chart` x-axis ticks | `(None)`, North, South | `No region (R8)`,
North, South |
The export leg needed one harness step: the dev admin holds no
`allowExport` grant (the built-in admin sets never carry it, by design),
so the browser's `/me/permissions` answer was patched to add `export` on
the scratch object. The CSV itself is the renderer's own snapshot.
- The report chart site was not in the card; the seat agreed in the card
thread to fold it in, as the same defect in the same file.
- `formatDimensionValue` in `@object-ui/core` and `plugin-dashboard` are
untouched; `declaredNullCategoryLabel` is a local copy of the
dashboard's rule rather than a new shared helper.
- eslint on the two touched files: 0 errors; the renderer's 5
`react-refresh` warnings are the same count as on main; the new test
file carries 8 `no-explicit-any` warnings on chart-prop reads, as the
sibling chart tests do.
## Verification
Head `ae73251f`: origin/main merged twice (`39951d2a` at `3b3fef03`,
`6dd6a534` at `ae73251f`), both clean; neither brought a change to
`ui/packages/plugin-report` or `ui/packages/core`, and no parallel
`nullLabel` fix exists on main.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 34 commands, all exit 0 at `5ed3317b` (the
same 3 paths as the head); `--ran` reconciles 34 derived, 34 run, 0
NOT-MEASURED, 0 UNRUN.
- `pnpm --dir ui exec vitest run packages/plugin-report/` at the head:
33 files, 307 passed, 7 skipped (the new file's 8 included).
- `pnpm exec turbo run type-check --filter=@object-ui/plugin-report` at
the head: 48 tasks, green.
- `pnpm --dir ui exec vitest run scripts/` at `5ed3317b`: 169 files
passed, 2 skipped. `node scripts/check-sdui-manifest.mjs` at the head:
0.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (eslint on every touched
file: 0 errors; the repo-wide run is CI's)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jUQvckLeFmnn4neyq3
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jUQvckLeFmnn4neyq3)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8dceec1 commit 606c32d
3 files changed
Lines changed: 407 additions & 10 deletions
File tree
- .changeset
- ui/packages/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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1022 | 1022 | | |
1023 | 1023 | | |
1024 | 1024 | | |
1025 | | - | |
| 1025 | + | |
1026 | 1026 | | |
1027 | 1027 | | |
1028 | 1028 | | |
| |||
1033 | 1033 | | |
1034 | 1034 | | |
1035 | 1035 | | |
1036 | | - | |
| 1036 | + | |
1037 | 1037 | | |
1038 | 1038 | | |
1039 | 1039 | | |
| |||
1076 | 1076 | | |
1077 | 1077 | | |
1078 | 1078 | | |
1079 | | - | |
| 1079 | + | |
1080 | 1080 | | |
1081 | 1081 | | |
1082 | 1082 | | |
| |||
1620 | 1620 | | |
1621 | 1621 | | |
1622 | 1622 | | |
1623 | | - | |
| 1623 | + | |
| 1624 | + | |
| 1625 | + | |
| 1626 | + | |
| 1627 | + | |
1624 | 1628 | | |
1625 | 1629 | | |
1626 | 1630 | | |
| |||
1737 | 1741 | | |
1738 | 1742 | | |
1739 | 1743 | | |
| 1744 | + | |
| 1745 | + | |
| 1746 | + | |
| 1747 | + | |
| 1748 | + | |
| 1749 | + | |
| 1750 | + | |
| 1751 | + | |
| 1752 | + | |
| 1753 | + | |
| 1754 | + | |
| 1755 | + | |
| 1756 | + | |
| 1757 | + | |
| 1758 | + | |
| 1759 | + | |
| 1760 | + | |
| 1761 | + | |
| 1762 | + | |
| 1763 | + | |
| 1764 | + | |
| 1765 | + | |
| 1766 | + | |
| 1767 | + | |
| 1768 | + | |
| 1769 | + | |
| 1770 | + | |
| 1771 | + | |
| 1772 | + | |
| 1773 | + | |
| 1774 | + | |
| 1775 | + | |
| 1776 | + | |
| 1777 | + | |
| 1778 | + | |
| 1779 | + | |
| 1780 | + | |
| 1781 | + | |
| 1782 | + | |
| 1783 | + | |
| 1784 | + | |
| 1785 | + | |
| 1786 | + | |
| 1787 | + | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
1740 | 1797 | | |
1741 | 1798 | | |
1742 | 1799 | | |
| |||
1766 | 1823 | | |
1767 | 1824 | | |
1768 | 1825 | | |
| 1826 | + | |
| 1827 | + | |
| 1828 | + | |
1769 | 1829 | | |
1770 | | - | |
1771 | | - | |
| 1830 | + | |
| 1831 | + | |
| 1832 | + | |
| 1833 | + | |
| 1834 | + | |
| 1835 | + | |
| 1836 | + | |
1772 | 1837 | | |
1773 | 1838 | | |
1774 | 1839 | | |
| |||
1866 | 1931 | | |
1867 | 1932 | | |
1868 | 1933 | | |
1869 | | - | |
| 1934 | + | |
1870 | 1935 | | |
1871 | 1936 | | |
1872 | 1937 | | |
1873 | 1938 | | |
1874 | 1939 | | |
1875 | 1940 | | |
1876 | | - | |
| 1941 | + | |
1877 | 1942 | | |
1878 | 1943 | | |
1879 | 1944 | | |
| |||
1987 | 2052 | | |
1988 | 2053 | | |
1989 | 2054 | | |
1990 | | - | |
| 2055 | + | |
1991 | 2056 | | |
1992 | 2057 | | |
1993 | 2058 | | |
| |||
2041 | 2106 | | |
2042 | 2107 | | |
2043 | 2108 | | |
2044 | | - | |
| 2109 | + | |
2045 | 2110 | | |
2046 | 2111 | | |
2047 | 2112 | | |
| |||
0 commit comments