@object-ui/plugin-report 4.0.12 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,244 @@
1
1
  # @object-ui/plugin-report
2
2
 
3
+ ## 4.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f196cf4: feat(plugin-report): popup picker for groupings + section-aware test ids
8
+
9
+ The matrix/summary "Group by" (rows) and "Columns axis" (cols) sections now
10
+ share the same searchable popup picker as the columns section, with a
11
+ commit-on-select single-pick mode wired through `FieldPickerDialog`.
12
+ - Per-row field buttons display the human-readable field label and open a
13
+ dialog scoped to swap that single field (already-used fields filtered out)
14
+ - "Add grouping" trigger uses the same dialog
15
+ - `GroupingsBuilder` accepts a `testIdPrefix` prop; ReportConfigPanel passes
16
+ `rows-grouping` and `cols-grouping` so both instances no longer share the
17
+ ambiguous `grouping-field-0` testid
18
+ - Bigger row spacing (h-7 / text-xs) — the old `text-[10px]` was unreadable
19
+
20
+ `FieldPickerDialog` gains:
21
+ - `commitOnSelect`: hides the Confirm/Cancel footer; clicking a row commits
22
+ - closes immediately (intended for `singleSelect` flows)
23
+ - `trigger`: custom trigger element override (used by the per-row field button)
24
+
25
+ - 079c3b2: feat(plugin-report): per-block field resolution for joined reports
26
+
27
+ Joined report blocks can override `objectName` to query a different
28
+ object than the container, but the editor was always offering the
29
+ container's fields — wrong field names, wrong types, broken granularity
30
+ and chart-axis filtering.
31
+
32
+ `ReportConfigPanel` now accepts an optional `getFieldsForObject`
33
+ resolver. `JoinedBlocksEditor` uses it to source fields for each
34
+ block based on `block.objectName ?? containerObjectName`, falling
35
+ back to the static `availableFields` when the resolver returns
36
+ `undefined` (unknown object).
37
+
38
+ `ReportView` wires the resolver against the app's loaded `objects`
39
+ list and reuses the same parsing path internally to derive its
40
+ top-level `availableFields`, removing the duplicated schema lookup.
41
+
42
+ 5 new RTL tests verify the resolver wiring, fallback behaviour,
43
+ add-block flow, and inline duplicate-name validation (111 plugin-report
44
+ tests green).
45
+
46
+ - ee1cc96: feat(plugin-report): joined-report block editor
47
+
48
+ `type: 'joined'` reports were a black hole in the editor — the type
49
+ selector exposed them but no UI knew how to edit the `blocks` array,
50
+ so users could neither create nor modify joined reports without
51
+ hand-editing JSON.
52
+
53
+ This change adds a `Blocks` section to the report editor, visible only
54
+ when `type === 'joined'`. Each block renders as a collapsible card with
55
+ its own name (required + unique-validated), label, description, block
56
+ type, object override, and reuses the existing `ColumnsEditor`,
57
+ `GroupingsBuilder`, `SpecFilterAdapter`, and `ChartConfig` builders so
58
+ every block behaves like a mini standalone report — matching the
59
+ runtime contract of `JoinedReportRenderer`.
60
+
61
+ Block-level validation is surfaced in the main `ValidationBanner`:
62
+ empty blocks array, missing or duplicate block names, and blocks
63
+ without columns all become editor-time errors so saves stay safe.
64
+
65
+ The non-joined sections (Columns / Rows / Columns axis / Filters /
66
+ Chart) are hidden when `type === 'joined'` since they live per-block
67
+ in the spec.
68
+
69
+ New exports from `@object-ui/plugin-report`:
70
+ - `JoinedBlocksEditor` — standalone component for embedding the
71
+ block editor anywhere.
72
+ - `validateJoinedBlocks` — pure helper returning translated
73
+ problem strings, suitable for custom validation banners.
74
+ - `ColumnsEditor`, `GroupingsBuilder`, `ChartConfig`,
75
+ `SpecFilterAdapter`, `normalizeColumns` are now exported so
76
+ downstream consumers can build their own report-editor surfaces.
77
+
78
+ i18n: added `report.editor.blocks*` / `report.editor.addBlock` /
79
+ `report.editor.removeBlock` / `report.editor.blockName*` /
80
+ `report.editor.blockLabel*` / `report.editor.blockDescription*` /
81
+ `report.editor.validationJoinedNeedsBlocks` /
82
+ `report.editor.validationBlockNameRequired` /
83
+ `report.editor.validationBlockNameDuplicate` /
84
+ `report.editor.validationBlockNeedsColumns` to en + zh.
85
+
86
+ - 0b032be: feat(plugin-report): replace inline column picker with a popup field picker
87
+
88
+ The columns section now opens a Dialog-based multi-select picker (`FieldPickerDialog`)
89
+ instead of rendering the unselected field list inline. The popup supports search,
90
+ batched multi-selection (commit several fields in one click), per-field type badges,
91
+ cancel-discards-pending semantics, and is fully i18n'd. Also fixes a latent
92
+ `ReferenceError: normalizeColumns is not defined` that crashed the editor whenever
93
+ the chart section was expanded.
94
+
95
+ - 4e7bc1b: **Report editor panel overhaul**
96
+
97
+ The report configuration panel is now safe to open on any spec-shape `Report` and only exposes fields that are actually persisted by `@objectstack/spec`.
98
+
99
+ `@object-ui/plugin-report`:
100
+ - Add a bidirectional `SpecFilterAdapter` so `ReportConfigPanel` can edit
101
+ spec `FilterCondition` filters (`{field: value}`, `{field: {$op: value}}`,
102
+ top-level `$and`/`$or`). Complex / nested filters fall back to a
103
+ read-only banner and are preserved verbatim on save.
104
+ - Drop sections that never round-tripped through the spec
105
+ (`conditionalFormatting`, `sections`, `export`, `schedule`, `appearance`)
106
+ and their helper components.
107
+ - Add type-driven section visibility: `tabular` shows Columns/Filters,
108
+ `summary` adds Rows + Chart, `matrix` adds Rows + Columns axis + Chart.
109
+ - New `GroupingsBuilder` covers `groupingsDown`/`groupingsAcross` with
110
+ `sortOrder` and date-aware `dateGranularity` controls.
111
+ - New `ColumnsEditor` lets users reorder picked columns, override labels,
112
+ set aggregates and choose a display format.
113
+ - Chart subset now mirrors the spec: chart `title`, `showLegend`,
114
+ `showDataLabels`, plus `funnel` (scatter removed).
115
+ - Validation banner highlights missing `objectName` and missing
116
+ rows/columns for `matrix`/`summary` reports.
117
+ - All editor labels and hints are i18n-driven (`report.editor.*`).
118
+ - 18 new unit tests cover the filter adapter round-trip.
119
+
120
+ `@object-ui/components`:
121
+ - `FilterBuilder` now guards against malformed external `value` props.
122
+ Previously a spec-shape filter (`{is_active: true}`) would crash the
123
+ component on first render; the builder now falls back to an empty
124
+ AND group whenever `value` is not a valid `FilterGroup`.
125
+
126
+ `@object-ui/i18n`:
127
+ - Add `report.editor.*` strings to `en` and `zh`.
128
+
129
+ - 8442c05: Improve report editor panel usability based on real-user browser testing:
130
+ - **Wider config panel** — the report editor now defaults to a `--config-panel-width`
131
+ of 440px (up from 280px), driven by a new optional `style` prop on
132
+ `ConfigPanelRenderer`. Long field labels, report titles, type labels, and filter
133
+ rows no longer truncate to "Account Na" / "kup" / "ct" / 1-character widths.
134
+ - **Disambiguated "Columns" sections** — for `summary` and `matrix` reports the
135
+ measure list is now labelled **"Values / 度量"** (pivot-style vocabulary) instead
136
+ of "Columns", which previously clashed with the matrix's pivot column axis
137
+ (also called "Columns / 列"). The two sections used to be indistinguishable.
138
+ New i18n key `report.editor.values` / `valuesHint` is shipped for all 10
139
+ locales (en, zh, ar, de, es, fr, ja, ko, pt, ru).
140
+ - **Reordered sections for matrix/summary** — the editor now surfaces _Rows_
141
+ and _Columns_ (the pivot axes) **before** _Values_, mirroring how a business
142
+ user thinks about a pivot table.
143
+ - **Per-row aggregate/format headers** — each column row in `ColumnsEditor` now
144
+ shows small "Aggregate" / "Format" labels above the respective selects, and
145
+ the row uses a 2-line layout so the label input has its own line. The cramped
146
+ 3-dropdowns-side-by-side layout at 10px font is gone.
147
+ - **Searchable field picker** — the "Add columns" list now has a search box,
148
+ a `filtered / total` counter, an empty-state message, and a scrollable bordered
149
+ container. New i18n keys: `report.editor.searchFields`,
150
+ `report.editor.noMatchingFields`.
151
+
152
+ ### Patch Changes
153
+
154
+ - 12c3b7c: refactor(plugin-report): split ReportConfigPanel.tsx (~1200 lines) into per-builder modules. The orchestrator file now only hosts `buildReportSchema`, `ValidationBanner`, and the public `ReportConfigPanel` component; each sub-editor (`SpecFilterAdapter`, `ColumnsEditor`, `GroupingsBuilder`, `ChartConfig`) lives in its own file alongside `editorTypes.ts` for shared types/constants. All existing exports are re-exported from `ReportConfigPanel` so test files and downstream consumers (`JoinedBlocksEditor`, `app-shell`) keep their current import paths. Pure refactor — no behavior change, 111 tests green.
155
+ - Updated dependencies [f196cf4]
156
+ - Updated dependencies [ee1cc96]
157
+ - Updated dependencies [0b032be]
158
+ - Updated dependencies [115d36a]
159
+ - Updated dependencies [4e7bc1b]
160
+ - Updated dependencies [8442c05]
161
+ - @object-ui/i18n@4.3.0
162
+ - @object-ui/components@4.3.0
163
+ - @object-ui/fields@4.3.0
164
+ - @object-ui/react@4.3.0
165
+ - @object-ui/plugin-grid@4.3.0
166
+ - @object-ui/types@4.3.0
167
+ - @object-ui/core@4.3.0
168
+
169
+ ## 4.2.1
170
+
171
+ ### Patch Changes
172
+
173
+ - @object-ui/types@4.2.1
174
+ - @object-ui/core@4.2.1
175
+ - @object-ui/i18n@4.2.1
176
+ - @object-ui/react@4.2.1
177
+ - @object-ui/components@4.2.1
178
+ - @object-ui/fields@4.2.1
179
+ - @object-ui/plugin-grid@4.2.1
180
+
181
+ ## 4.2.0
182
+
183
+ ### Minor Changes
184
+
185
+ - 650392e: MatrixRenderer now displays i18n-translated labels for picklist (`select` / `status`) groupings instead of raw values (e.g. `Best Case` / `Commit` / `Pipeline` instead of `best_case` / `commit` / `pipeline`). Field labels in the corner cell, row/column total labels, and the `(Empty)` / `(All)` placeholders are also fully translated. Adds `report.*` keys to `en` and `zh` locale bundles.
186
+ - 26f5fce: Simplify report identity: replace the dashboard-style KPI grid with a compact "Totals" strip so reports look like reports (table-first with a grand total), not like mini-dashboards.
187
+ - `SpecReportGrid` now renders one inline `Totals: Label1: value Label2: value …` strip above the chart and table, styled as a muted single-line band — clearly subordinate to the data grid below.
188
+ - The Totals strip is now also shown for `tabular` reports when they declare aggregating columns (matches Salesforce's "Grand Total" convention).
189
+ - Drop the duplicate chart title `<div>`: the chart component already renders its own title from `report.chart.title`.
190
+ - Test ids renamed: `spec-report-kpis` → `spec-report-totals`, `spec-report-kpi-${key}` → `spec-report-total-${key}`.
191
+
192
+ Visual distinction from dashboards is now intentional: dashboard widgets use prominent floating KPI cards to convey "headline numbers"; report Totals describe the single dataset on the page and are intentionally compact.
193
+
194
+ - 84b4bf1: Summary reports now render i18n-translated labels in the chart axis, chart series legend, and totals strip. `buildChartData` accepts a new `labels` parameter so callers (currently `SpecReportGrid`) can supply field/column/aggregate/value resolvers. Replaces raw column keys (e.g. `Count of case_number`) and raw picklist values (e.g. `closed`, `in_progress`) with their translated display labels (e.g. `案例编号 · 计数`, `已关闭`, `处理中`). Adds `report.totals` locale key.
195
+
196
+ ### Patch Changes
197
+
198
+ - Updated dependencies [eb738bd]
199
+ - Updated dependencies [650392e]
200
+ - Updated dependencies [84b4bf1]
201
+ - @object-ui/i18n@4.2.0
202
+ - @object-ui/components@4.2.0
203
+ - @object-ui/fields@4.2.0
204
+ - @object-ui/react@4.2.0
205
+ - @object-ui/plugin-grid@4.2.0
206
+ - @object-ui/types@4.2.0
207
+ - @object-ui/core@4.2.0
208
+
209
+ ## 4.1.0
210
+
211
+ ### Minor Changes
212
+
213
+ - b4ce9e2: Fix summary reports: render chart + KPIs, correct empty-table on server-aggregated data.
214
+ - `plugin-report`: `SpecReportGrid` now renders a KPI strip (per aggregating column) and a chart section above the grid for `summary` reports. KPI section auto-hides when no aggregating columns. New `buildChartData()` adapter buckets aggregated `ReportRow[]` to chart-ready data, auto-sorts pie/funnel descending, and falls back to row count when the chart `yAxis` points at a non-numeric column. When the data is server-aggregated, the grid switches columns to `[groupings, ${field}__${agg}]` so cells aren't empty against a raw-row column schema.
215
+ - `plugin-charts`: register `'column'` as an alias of `'bar'` in `ChartRenderer` / `AdvancedChartImpl` (Recharts only has `BarChart`).
216
+ - `app-shell`: `ReportView` now routes any object-backed report (matrix/joined/summary/tabular/columns/groupingsAcross) through the spec `ReportRenderer`; fully-legacy `fields`+`data` schemas still use `ReportViewer`.
217
+
218
+ - b42a0d0: Server-side `dateGranularity` pushdown.
219
+
220
+ `useReportData()` reports with `{ groupBy: [{ field, dateGranularity }] }`
221
+ are now aggregated directly in the database via native SQL (`strftime` /
222
+ `to_char` / `date_format`) instead of fetching raw rows and bucketing in
223
+ Node. The framework's `driver-sql` advertises per-granularity support via
224
+ `IDataDriver.supports.queryDateGranularity` and the engine transparently
225
+ falls back to in-memory bucketing only when the dialect can't express a
226
+ given granularity (notably SQLite `week`, which needs `strftime('%V')`
227
+ added in SQLite 3.46). Output bucket labels (`2026-Q2`, `2026-01-15`,
228
+ `2026-W23`, …) are byte-for-byte identical between paths so drill
229
+ `groupKey` filters compose correctly across SQL and in-memory routes.
230
+
231
+ Requires framework ≥ commit `b26d217c`.
232
+
233
+ ### Patch Changes
234
+
235
+ - @object-ui/types@4.1.0
236
+ - @object-ui/core@4.1.0
237
+ - @object-ui/react@4.1.0
238
+ - @object-ui/components@4.1.0
239
+ - @object-ui/fields@4.1.0
240
+ - @object-ui/plugin-grid@4.1.0
241
+
3
242
  ## 4.0.12
4
243
 
5
244
  ### Patch Changes
package/README.md CHANGED
@@ -4,15 +4,15 @@ Report components for Object UI — build, view, render, and export reports with
4
4
 
5
5
  ## Features
6
6
 
7
- - 📊 **Report Builder** - Visual drag-and-drop report construction
8
- - 👁️ **Report Viewer** - Interactive report viewing with toolbar controls
9
- - 🎨 **Type-aware cells** - select → Badge, lookup → link, boolean → ✓/✗, email/url/phone → mailto/external/tel links, image → thumbnail, etc. (auto-hydrated from object metadata)
10
- - 🖨️ **Report Renderer** - Render reports from JSON definitions with charts
11
- - 📤 **Multi-Format Export** - Export to CSV, JSON, HTML, PDF, and Excel
12
- - 🔄 **Live Data Export** - Export with real-time data via `exportWithLiveData`
13
- - 📈 **Excel Formulas** - Export Excel files with live formulas via `exportExcelWithFormulas`
14
- - ⏰ **Schedule Config** - Configure recurring report generation and delivery
15
- - 📦 **Auto-registered** - Components register with `ComponentRegistry` on import
7
+ - 🧩 **Four spec report variants** — `tabular` / `summary` / `matrix` / `joined`, dispatched by a single `<ReportRenderer schema={...}>`
8
+ - 🧮 **Server-side aggregation** — `useReportData()` posts spec `QueryAST` to `POST /api/v1/data/:object/query`; transparent in-memory fallback
9
+ - 📅 **Date bucketing** — `dateGranularity: day|week|month|quarter|year` on `groupingsAcross` / `groupingsDown`
10
+ - 🪜 **Multi-level grouping + totals** — row totals, column totals, grand totals for matrix; tabular/summary delegate to `ObjectGrid`
11
+ - 🎯 **Cell drill-down** — every aggregated cell dispatches a `drill` action via `ActionRunner`; targets List view or a nested Report (M3)
12
+ - 🧱 **Joined reports** — vertically stacked sub-reports; each block owns its own `objectName`, filter and data fetch
13
+ - 🎨 **Type-aware cells** — `select` → Badge, `lookup` → link, `boolean` → ✓/✗, `email`/`url`/`phone` → links, `image` → thumbnail (auto-hydrated from object metadata)
14
+ - 🖨️ **Multi-format export** — CSV, JSON, HTML, PDF, Excel; live-data and Excel-formula variants
15
+ - 📦 **Auto-registered** — components register with `ComponentRegistry` on import; embed via `{ "type": "spec-report", "report": {...} }`
16
16
 
17
17
  ## Installation
18
18
 
@@ -49,33 +49,115 @@ function EmbeddedReport() {
49
49
  }
50
50
  ```
51
51
 
52
- ## API
52
+ ## Spec Reports — the four variants
53
53
 
54
- ### ReportBuilder
54
+ The plugin renders any `Report` defined by `@objectstack/spec`:
55
55
 
56
- Visual report construction interface:
56
+ ```ts
57
+ import type { ReportInput } from '@objectstack/spec/ui';
58
+ import { ReportRenderer } from '@object-ui/plugin-report';
57
59
 
58
- ```tsx
59
- <ReportBuilder report={initialReport} />
60
+ const report: ReportInput = {
61
+ name: 'opp_by_stage',
62
+ objectName: 'opportunity',
63
+ type: 'summary',
64
+ columns: [
65
+ { field: 'stage' },
66
+ { field: 'amount', aggregate: 'sum' },
67
+ { field: 'id', label: 'Deals', aggregate: 'count' },
68
+ ],
69
+ groupingsDown: [{ field: 'stage', sortOrder: 'asc' }],
70
+ };
71
+
72
+ <ReportRenderer schema={report} dataSource={ds} />
60
73
  ```
61
74
 
62
- ### ReportViewer
75
+ | `type` | Description |
76
+ | --------- | ---------------------------------------------------- |
77
+ | `tabular` | Flat record list |
78
+ | `summary` | Single-axis grouped + aggregated |
79
+ | `matrix` | Row × column pivot with cell aggregates and totals |
80
+ | `joined` | Vertically stacked sub-reports, each with own data |
63
81
 
64
- Interactive report viewer with toolbar:
82
+ ### Matrix (row × column pivot)
65
83
 
66
- ```tsx
67
- <ReportViewer report={reportDefinition} showToolbar />
84
+ ```ts
85
+ {
86
+ name: 'pipeline_by_quarter',
87
+ objectName: 'opportunity',
88
+ type: 'matrix',
89
+ columns: [{ field: 'amount', label: 'Pipeline', aggregate: 'sum' }],
90
+ groupingsDown: [{ field: 'forecast_category' }],
91
+ groupingsAcross: [{ field: 'close_date', dateGranularity: 'quarter' }],
92
+ }
68
93
  ```
69
94
 
70
- ### ReportRenderer
95
+ `dateGranularity` accepts `day | week | month | quarter | year` and is
96
+ pushed down to the server-side aggregator.
71
97
 
72
- Renders a report from a JSON chart configuration:
98
+ ### Joined (M3)
99
+
100
+ ```ts
101
+ {
102
+ name: 'churn_signals',
103
+ objectName: 'account', // container default
104
+ type: 'joined',
105
+ columns: [],
106
+ blocks: [
107
+ { name: 'at_risk', type: 'summary', columns: [...], filter: {...} },
108
+ { name: 'lost', type: 'summary', objectName: 'opportunity', columns: [...], filter: {...} },
109
+ ],
110
+ }
111
+ ```
112
+
113
+ Block rules: `objectName` falls back to the container; `filter` is ANDed
114
+ with the container's; each block runs an isolated `useReportData()` call;
115
+ `block.type` must not be `joined` (no recursion).
116
+
117
+ ## Server-side aggregation + drill-down
118
+
119
+ `useReportData()` translates a `Report` into spec `QueryAST` and posts it
120
+ to `POST /api/v1/data/:object/query`. If the endpoint is unavailable it
121
+ falls back transparently to `dataSource.find()` + client-side aggregation.
122
+
123
+ Every aggregated cell dispatches a `drill` action through `ActionRunner`:
73
124
 
74
125
  ```tsx
75
- <ReportRenderer title="Revenue" description="Q4 Revenue" chart={chartConfig} />
126
+ import { registerDrillHandler } from '@object-ui/plugin-report';
127
+ registerDrillHandler(actionRunner, { navigate: router.push });
128
+ ```
129
+
130
+ Drill targets:
131
+ 1. **List view** — default; navigates to the filtered records.
132
+ 2. **Report drawer** — if the host widget declares `drillDown.report`,
133
+ the click opens a side drawer that renders that report scoped to the
134
+ cell's group key (composes dashboard → report → record).
135
+
136
+ ## Filter-time date helpers — current limitation
137
+
138
+ The server does **not** currently evaluate `` cel`...` `` expressions
139
+ embedded in filter values. Use module-load ISO strings instead:
140
+
141
+ ```ts
142
+ const daysAgo = (n: number): string => {
143
+ const d = new Date();
144
+ d.setUTCDate(d.getUTCDate() - n);
145
+ return d.toISOString().slice(0, 10);
146
+ };
147
+
148
+ filter: { close_date: { $gte: daysAgo(30) } }
76
149
  ```
77
150
 
78
- ### Export Functions
151
+ See the bundled CRM `customer_churn_signals` demo for the full pattern.
152
+ Native filter-time CEL evaluation is tracked for a future major version.
153
+
154
+ ## Legacy components
155
+
156
+ `ReportBuilder`, `ReportViewer` and the export functions below remain
157
+ available for legacy presentation-layer reports and are not affected by
158
+ the spec-driven pipeline above.
159
+
160
+
79
161
 
80
162
  Export reports in multiple formats:
81
163