Repository navigation
Commit e5c8ea7
fix(plugin-report): a report lays out a select dimension in the field's declared option order (objectui#12243) (#12261)
Fixes #12243
Clause-②: no
Item 1 of the list card objectui#12211.
A dataset-bound report now lays out a select dimension in the field's
declared option order. This covers the table rows, a matrix's row and
column headers, and the embedded chart's categories. Before, they came
in the dataset's `GROUP BY` order, which is the stored values'
alphabetical order. A stage column read Closed Won, Needs Analysis,
Negotiation, Proposal, Prospecting, Qualification, and the bar chart
beside it drew the same order.
## What changed
The code is all in
`packages/plugin-report/src/DatasetReportRenderer.tsx`. Round 2 adds
text only (see the next section).
- **Which rule.** It is the reading objectui#11809 (PR objectui#11830)
set for grid groups and dashboard charts:
- the declared options come first, in their order;
- a value the options do not declare comes next, in the order the
dataset returned it;
- the empty bucket comes last;
- a dimension whose field declares no options (a lookup, a text field, a
date) keeps the order the dataset returned it in.
- **Several dimensions.** The layout works one level at a time,
outermost first. The items are bucketed by the level's value, with the
buckets kept in the order they first arrive, and each bucket is then
laid out by the next level. So in Owner → Stage the owners keep their
order and the stages follow theirs within each owner. In Status and
Priority the statuses follow their order, and the priorities follow
theirs within each status. Rows the dataset returns grouped (as `GROUP
BY` returns them) keep their outer order exactly.
- **An explicit report `order` wins.** When the report's `order` reaches
a selection (after `scopeOrder`, the narrowing the query itself uses),
that table or chart keeps the server's order untouched. The table and
its chart decide this separately, because each sends its own selection.
- **One read, one reader.** The table and the matrix now read
`useDatasetDimensionMeta`, the hook the chart already reads, instead of
the label-only `useDatasetDimensionLabels`. That is still one metadata
read per component, as before. From it a new module-private helper,
`deriveDimensionDisplay`, derives two things:
- the label maps, with `deriveDimensionLabelMaps` and the same arguments
the label hook passes;
- the ranks, through core's `localizeFieldOptions` →
`buildCategoryOrder` → `buildCategoryRank`, which is the chain the
chart's `categoryOrder` already uses.
The ranks key both the stored value and the localized label, so they
match the relabeled rows. `@object-ui/core` is not changed.
- **Drill alignment.** The table lays out a list of indices and never
builds a reordered copy of the rows. A drill still reads
`state.rows[i]`, `drillRawRows[i]` and `drillRanges[i]` at the dataset's
own index. The matrix moves only its two header lists, and every cell is
still looked up by bucket id.
- **The chart.** Its rows are put in the declared order before
`buildChartSeries` runs, as `DatasetWidget` does since objectui#11809.
`AdvancedChartImpl`'s published `categoryOrder` prop is not widened, for
the reason PR objectui#11830 measured: it would override an explicit
report `order` on every chart. The funnel still receives `categoryOrder`
(a CONTROL pins it), so its order is unchanged.
- **Unchanged surface.** No prop, export, type member, spec key or
language-pack key is added. The package entry and `exports` are
unchanged.
## Round 2: file-surface amendment
The seat's amendment (comment `6126029177` on the card) took option A of
the round-1 report and added exactly three files, text only, in commit
`b9501024`. There is no code change beyond round 1. The changeset stays
a `patch` for `@object-ui/plugin-report`, and `Clause-②: no` stands.
- `packages/plugin-report/src/useDatasetDimensionLabels.ts`: the header
sentence that named `DatasetReportChart` as the one call site of
`useDatasetDimensionMeta` now says the table and the matrix read it too.
Comment only.
- `content/docs/plugins/plugin-report.mdx` and
`packages/plugin-report/README.md`: each Ordering section gains the
select-dimension default. With no `order`, a select dimension follows
its declared option order, with undeclared values next and the empty
bucket last. An authored `order` keeps the server's order.
Gates on `b9501024`, each exit read before any pipe. `origin/main` had
not moved from `7578323a`, so nothing was merged and the vitest run was
not repeated.
- Exit 0: `check:doc-fences`, `docs:check-links` ("Links are valid
across 17 scan roots"), `check:control-bytes`,
`check:new-line-citations` (0 new citations),
`scripts/check-changeset-presence.mjs` (3 source files of 1 released
package, 1 changeset) and `scripts/check-changeset-no-major.mjs`.
- `check:doc-snippets` and `check:doc-examples` first answered
PRECONDITION NOT MET (exit 2) on an unbuilt tree. After the build they
name (`turbo run build` over their `--build-filter`, 35 of 35 tasks),
both exit 0.
- `pnpm --filter @object-ui/plugin-report lint`: exit 0, 0 errors, the
same 99 warnings as round 1, none in the edited header file.
## Measured: which path draws the three reports
Read from HotCRM's sources on its `main` branch
(`src/sales/reports/opportunity.report.ts` and
`src/service/reports/case.report.ts`). The card's hosted pass ran HotCRM
4.0.1, and I did not read that tag itself.
- *Opportunities by Stage* is dataset `opportunity_metrics`, `type:
'summary'`, rows `stage`, with a `bar` chart on `stage`.
- *Opportunity Funnel by Owner → Stage* is `summary`, rows `owner` then
`stage`, with a `funnel` chart on `stage`.
- *Cases by Status and Priority* is `summary`, rows `status` then
`priority`, with a `bar` chart on `status`.
- None of the three declares an `order`.
All three are dataset-bound, so `ReportRenderer` routes them to
`DatasetReportRenderer`. Their tables are `DatasetReportTable` and their
charts `DatasetReportChart`. `ReportViewer`'s `groupData` (a
`localeCompare` sort) is reached only by the pre-9.0 bridge and by
`report-viewer` nodes, so none of the three reaches it. It is left
alone; see the Acceptance notes.
The fixtures use HotCRM's declared options (`OPPORTUNITY_STAGE_OPTIONS`,
and `crm_case`'s `status` and `priority`), read from the same sources.
## Tests
New pin file:
`packages/plugin-report/src/__tests__/DatasetReportRenderer.selectOptionOrder-12243.test.tsx`,
10 tests. Each one drives the real `DatasetReportRenderer`. Each one
also goes through the real dimension-metadata read, which is served by a
recording `fetch` double for `/api/v1/meta/object/NAME` only.
- **Opportunities by Stage, table.** The rows read Prospecting,
Qualification, Needs Analysis, Proposal, Negotiation, Closed Won, then
the undeclared `legacy_stage`, then the empty bucket. Each row keeps its
own measures.
- **Opportunities by Stage, bar chart.** The categories come in the same
order, with the empty bucket labelled last.
- **zh-CN, as measured on hosted HotCRM.** The rows read 寻找客户, 资格审查,
需求分析, 提案, 谈判, 成交, and the chart follows them.
- **Drill on a moved row.** Clicking Prospecting, which arrived seventh,
emits its own `groupKey` and `objectFilter` (`stage: 'prospecting'`).
- **Owner → Stage.** The owners keep their arrival order (stored-id
order, deliberately not name order), and the stages follow their
declared order within each owner.
- **Status and Priority.** Both levels follow their declared order, and
the bar chart follows status.
- **Matrix.** The row and column headers both follow their declared
order, and every cell stays under its own headers.
- **CONTROLs.** A dimension whose field declares no options keeps the
dataset's order. An explicit `order` (total amount, descending) keeps
the server's order in both the table and the chart, and is still sent to
the server. The funnel still receives its declared `categoryOrder`.
**Ablation**, run on `67e44752` (the fix and the pins, both committed
first). The mutation went through objectstack's
`scripts/ablation-replace.mjs`, which replaced the guard line of
`arrangeByDeclaredOptions` with an unconditional `return null`. The tool
reported the anchor going from 1 hit to 0 and the blob going from
`493a305f372f` to `bb7c508461cb`.
- Result: **7 failed, 3 passed**. The 3 that passed are the three
CONTROLs, which is the split written down before the run.
- Restore: the blob after restore is `493a305f372f`, which equals HEAD,
and `git diff HEAD` is empty. A separate check afterwards counted 0
marker hits and 1 anchor hit, with a clean `git status`.
- The test imports `../DatasetReportRenderer` relatively, so it resolves
to `src/`, and no build was involved.
## Local gates
Run from the worktree root on `77cd31fc`, the round-1 head. That head is
after merging `origin/main` at `7578323a`. The merge brought in
`@object-ui/types` and `@object-ui/i18n` changes, so the dependency
closure was rebuilt (`pnpm --filter '@object-ui/plugin-report^...' run
build`, exit 0). Each exit code was read before any pipe.
- `pnpm exec vitest run packages/plugin-report/`: exit 0. 27 of 27 files
passed, 249 tests passed and 7 skipped.
- `pnpm --filter @object-ui/plugin-report type-check`: exit 0. `tsc -p
tsconfig.test.json --listFilesOnly` includes the new pin file.
- `pnpm --filter @object-ui/plugin-report lint` (`eslint .`): exit 0, 0
errors. The new pin file raises no warning. The five warnings in
`DatasetReportRenderer.tsx` are the same five the base version raises.
- Consumers that render reports, run as a CONTROL: three `apps/console`
`drillDownReport.*` suites, four `app-shell` `ReportView.*` suites,
`ReportPreview.dataset` and `dataPreviews.i18n-10862-s3`. 9 of 9 files
passed, 80 of 80 tests.
- Each of these exits 0: `check:control-bytes`,
`check:new-line-citations` (0 new citations), `check:vi-mock-inherit`,
`check:vi-mock-specifiers`, `check:test-path-roots`,
`check:vi-mock-override-shape`, `check:changeset-claims`,
`check:pending-changeset-literals`, `check:phantom-deps`,
`check:self-import`, `check:shell-escape-residue`,
`check:unreferenced-sources`, `check:i18n-keys`,
`scripts/check-changeset-no-major.mjs` and
`scripts/check-changeset-presence.mjs` (2 source files of 1 released
package, 1 changeset).
- `node scripts/check-governed-queue-guard.mjs --test` over the three
paths prints NOT GOVERNED.
The changeset is `.changeset/12243-report-group-option-order.md`, a
`patch` for `@object-ui/plugin-report`.
## Acceptance notes
- **Done in round 2.** The header of
`packages/plugin-report/src/useDatasetDimensionLabels.ts` called
`DatasetReportChart` the one call site of `useDatasetDimensionMeta`,
which this change made false. That sentence now names the table and the
matrix too.
- **Done in round 2.** AGENTS.md rule 2 asks for docs that reflect the
code. The Ordering sections of `content/docs/plugins/plugin-report.mdx`
and `packages/plugin-report/README.md` now state the select-dimension
default beside the authored `order` and the time-axis default.
- **Same rule, two copies.** `declaredOptionPosition` here is a
module-private copy of the helper with the same name in
`plugin-dashboard`'s `DatasetWidget.tsx`. Each is four lines over core's
rank map. Lifting it into `@object-ui/core` next to `buildCategoryRank`
would make it one copy, but the claim ruled core read-only. Carrier:
none.
- **`ReportViewer`'s `groupData`** (the presentation viewer) still sorts
groups with `localeCompare`. None of the three reports reaches it, and
its schema carries no field options to rank by. Not changed. Carrier:
none.
- **Undeclared values are ordered as the chart orders them.** Among
themselves they keep the dataset's order, as on the dashboard chart and
the funnel. The grid sorts them by label instead. The ruling sets only
that they come after the declared options.
- **NOT MEASURED: the hosted console in a browser.** The pins drive the
real renderer with HotCRM's declared options and the hosted zh-CN
labels.
The dev run is session `session_01G5TSaEwbEQsKMjhzTB4rEd` (a subagent of
the `domain:ui` seat 2 PM).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ee5c491 commit e5c8ea7
6 files changed
Lines changed: 787 additions & 21 deletions
File tree
- .changeset
- content/docs/plugins
- 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 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
191 | 191 | | |
192 | 192 | | |
193 | 193 | | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
194 | 203 | | |
195 | 204 | | |
196 | 205 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
227 | 227 | | |
228 | 228 | | |
229 | 229 | | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
230 | 238 | | |
231 | 239 | | |
232 | 240 | | |
| |||
0 commit comments