Skip to content

Commit e5c8ea7

Browse files
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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@object-ui/plugin-report': patch
3+
---
4+
5+
fix(plugin-report): a report lays out a select dimension in the field's declared option order (objectui#12243)
6+
7+
A dataset-bound report used to show its groups in the order the dataset returned them, which is the
8+
stored values' alphabetical order. A stage dimension read Closed Won, Needs Analysis, Negotiation,
9+
Proposal, Prospecting, Qualification, and a bar chart beside the table drew the same order.
10+
11+
When the report declares no `order`, the table rows, a matrix's row and column headers and the
12+
embedded chart's categories now follow each select dimension's declared option order, the reading
13+
grid groups and dashboard charts already use (objectui#11809):
14+
15+
- the declared options come first, in their order;
16+
- a value the options do not declare comes next, in the order the dataset returned it;
17+
- the empty bucket comes last.
18+
19+
With two grouping dimensions, the outer one is laid out first and the inner one within each outer
20+
group. A dimension whose field declares no options (a lookup, a text field, a date) keeps the order
21+
the dataset returned it in.
22+
23+
A report that declares its own `order` keeps the server's order unchanged. A drill on a row still
24+
opens that row's records.

‎content/docs/plugins/plugin-report.mdx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ reads left-to-right in row-arrival order, so ordering by the across dimension is
191191
what makes the columns come out in that order; declaring nothing still reads
192192
correctly, because a selected time dimension defaults to ascending.
193193

194+
With no `order`, a **select** dimension follows its field's declared option
195+
order: the table rows, a matrix's row and column headers, and the embedded
196+
chart's categories. The declared options come first, in their order; a value the
197+
options do not declare comes next, in the order the dataset returned it; the
198+
empty bucket comes last. With several grouping dimensions the outer one is laid
199+
out first and the inner one within each outer group, and a dimension whose field
200+
declares no options keeps the order the dataset returned it in. An authored
201+
`order` keeps the server's order.
202+
194203
## Embedded chart
195204

196205
A report may carry a `chart` object rendered over the same selection. Its

‎packages/plugin-report/README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,14 @@ order, so ordering by the across dimension is what fixes the column order;
227227
declaring nothing still reads correctly, because a selected time dimension
228228
defaults to ascending.
229229

230+
With no `order`, a **select** dimension follows its field's declared option
231+
order (table rows, matrix headers on both axes, the embedded chart's
232+
categories): declared options first, values the options do not declare next in
233+
the dataset's order, the empty bucket last. With several grouping dimensions the
234+
outer one is laid out first and the inner one within each outer group; a
235+
dimension whose field declares no options keeps the dataset's order. An
236+
authored `order` keeps the server's order.
237+
230238
### Embedded chart
231239

232240
`chart.type` is one of the spec's chart names; `xAxis` / `yAxis` are **bare

0 commit comments

Comments
 (0)