@dforge-core/dforge-mcp 0.1.2 → 0.1.4

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.
@@ -1,152 +1,114 @@
1
1
  # Reports Reference
2
2
 
3
- Reports are **query-driven visualizations** — charts, KPIs, pivot tables, grids — backed by datasets. Unlike data views (which target one entity), reports can combine multiple datasets and render any layout.
3
+ Reports are **query-driven visualizations** — charts, KPI cards, pivot tables, tables — backed by datasets. Unlike data views (which target one entity), a report can combine **multiple datasets** and lay out several panels.
4
4
 
5
- Lives in: `ui/reports.json` or `ui/reports/<report_code>.json`
5
+ Lives in: `ui/reports.json` (map of `report_code` → report). Not listed in `manifest.json` — the install pipeline picks up `ui/reports.json` automatically.
6
6
  SP files: `logic/reports/rpt_*.sql`
7
7
 
8
8
  ## Structure
9
9
 
10
+ A report has `description`, a `datasets` map, a `layout` object (`{ panels: [...] }`), and optional `parameters`. Panels reference datasets by code via `datasetCd`.
11
+
10
12
  ```json
11
13
  {
12
14
  "sales_pipeline": {
13
15
  "description": "Open opportunities by stage",
14
16
  "datasets": {
15
- "ds_pipeline": {
17
+ "pipeline": {
16
18
  "caption": "Pipeline Data",
17
- "entityCode": "opportunity",
18
- "columns": ["stage", "total_amount", "account"],
19
- "filter": {
20
- "g": "and",
21
- "i": [
22
- { "c": "stage", "o": "!in", "v": ["Closed Won", "Closed Lost"] }
23
- ]
24
- },
25
- "groupBy": ["stage"],
26
- "aggregations": {
27
- "opp_count": { "func": "count", "column": "*" },
28
- "total_value": { "func": "sum", "column": "total_amount" }
19
+ "datasetType": "Q",
20
+ "query": {
21
+ "entityCd": "opportunity",
22
+ "columns": ["stage", "amount", "lead_source", "customer.name"],
23
+ "filter": {
24
+ "g": "and",
25
+ "i": [ { "c": "stage", "o": "nIn", "v": ["Closed Won", "Closed Lost"] } ]
26
+ },
27
+ "sort": [ { "c": "amount", "d": "desc" } ]
29
28
  }
30
29
  }
31
30
  },
32
- "layout": [
33
- {
34
- "vizType": "bar",
35
- "datasetCd": "ds_pipeline",
36
- "title": "Pipeline Value by Stage",
37
- "config": {
38
- "categoryCol": "stage",
39
- "valueCol": "total_value",
40
- "agg": "sum"
41
- }
42
- },
43
- {
44
- "vizType": "grid",
45
- "datasetCd": "ds_pipeline",
46
- "title": "Pipeline Detail"
47
- }
48
- ],
49
- "params": {
50
- "owner_filter": {
51
- "fieldTypeCd": "user",
52
- "label": "Filter by Owner",
53
- "required": false
54
- },
55
- "start_date": {
56
- "fieldTypeCd": "date",
57
- "label": "Start Date",
58
- "required": false,
59
- "default": "=STARTMONTH()"
60
- }
31
+ "layout": {
32
+ "panels": [
33
+ {
34
+ "vizType": "chart",
35
+ "datasetCd": "pipeline",
36
+ "title": "Pipeline Value by Stage",
37
+ "config": { "chartType": "bar", "categoryCol": "stage", "valueCol": "amount", "agg": "sum", "chartSize": "l" }
38
+ },
39
+ { "vizType": "table", "datasetCd": "pipeline", "title": "Pipeline Detail" }
40
+ ]
41
+ },
42
+ "parameters": {
43
+ "min_amount": { "fieldTypeCd": "number", "label": "Min Amount", "required": false, "default": 0 }
61
44
  }
62
45
  }
63
46
  }
64
47
  ```
65
48
 
49
+ **Aggregation happens in the viz, not the dataset.** A dataset selects raw columns; the chart/KPI config aggregates them (`agg`, `metrics`). There is no dataset-level `groupBy`/`aggregations` — do not use them.
50
+
66
51
  ## Dataset types
67
52
 
68
53
  ### Entity query dataset (most common)
69
54
 
70
- Queries an entity's data using the platform's query builder. Filters use the standard JSON filter format (see `references/filters.md`).
55
+ `datasetType: "Q"` (the default) with a `query` object using the platform query builder. Filters use the standard JSON filter format (see `references/filters.md`).
71
56
 
72
57
  ```json
73
- "ds_sales": {
58
+ "sales": {
74
59
  "caption": "Sales Data",
75
- "entityCode": "opportunity",
76
- "columns": ["stage", "total_amount", "close_date", "account"],
77
- "filter": { "c": "stage", "o": "!=", "v": "Closed Lost" },
78
- "sort": [{ "column_cd": "close_date", "direction": "desc" }],
79
- "groupBy": ["stage"],
80
- "aggregations": {
81
- "deal_count": { "func": "count", "column": "*" },
82
- "total_value": { "func": "sum", "column": "total_amount" },
83
- "avg_value": { "func": "avg", "column": "total_amount" }
84
- }
60
+ "datasetType": "Q",
61
+ "query": {
62
+ "entityCd": "opportunity",
63
+ "columns": ["stage", "amount", "close_date", "customer.name"],
64
+ "filter": { "c": "stage", "o": "ne", "v": "Closed Lost" },
65
+ "sort": [ { "c": "close_date", "d": "desc" } ]
66
+ },
67
+ "columnsDef": { "amount": { "label": "Deal Value" }, "customer.name": { "label": "Account" } }
85
68
  }
86
69
  ```
87
70
 
88
- Aggregation functions: `count`, `sum`, `avg`, `min`, `max`.
71
+ - `query.entityCd` source entity code (**not** `entityCode`).
72
+ - `query.columns` — column codes; supports dot navigation (`customer.name`).
73
+ - `query.filter` — canonical filter (`{c,o,v}` or `{g,i:[...]}`); use `@param_code` to reference a parameter.
74
+ - `query.sort` — `[{ c, d }]` where `d` ∈ `asc | desc`.
75
+ - `columnsDef` — optional per-column display overrides (`{ label, visible, width }`).
89
76
 
90
77
  ### Stored procedure dataset (developer path)
91
78
 
92
- For complex SQL that the entity query can't express — window functions, CTEs, cross-schema joins, conditional aggregation, multi-result sets.
79
+ For SQL the query builder can't express — window functions, CTEs, cross-schema joins, conditional aggregation, multi-result sets.
93
80
 
94
81
  ```json
95
- "aging_data": {
82
+ "aging": {
96
83
  "caption": "AR Aging",
97
84
  "datasetType": "S",
98
- "sp": "rpt_ar_aging",
85
+ "spCd": "rpt_ar_aging",
99
86
  "params": {
100
- "as_of_date": {
101
- "fieldTypeCd": "date",
102
- "label": "As of Date",
103
- "required": true,
104
- "default": "=NOW()"
105
- },
106
- "customer_id": {
107
- "fieldTypeCd": "lookup",
108
- "label": "Customer",
109
- "required": false,
110
- "params": { "entityCd": "customer" }
111
- }
87
+ "as_of_date": { "fieldTypeCd": "date", "label": "As of Date", "required": true, "default": "=NOW()" },
88
+ "customer_id": { "fieldTypeCd": "lookup", "label": "Customer", "required": false, "params": { "entityCd": "account" } }
112
89
  },
113
90
  "columnsDef": {
114
- "customer_name": {
115
- "label": "Customer",
116
- "fieldTypeCd": "text",
117
- "baseDatatypeCd": "string",
118
- "width": 200
119
- },
120
- "current_amount": {
121
- "label": "Current",
122
- "fieldTypeCd": "number",
123
- "baseDatatypeCd": "number",
124
- "format": "#,##0.00",
125
- "width": 120
126
- },
127
- "days_30": {
128
- "label": "1-30 Days",
129
- "fieldTypeCd": "number",
130
- "baseDatatypeCd": "number",
131
- "format": "#,##0.00",
132
- "width": 120
133
- },
134
- "total": {
135
- "label": "Total",
136
- "fieldTypeCd": "number",
137
- "baseDatatypeCd": "number",
138
- "format": "#,##0.00",
139
- "width": 130
140
- }
91
+ "customer_name": { "label": "Customer", "fieldTypeCd": "text", "baseDatatypeCd": "string", "width": 200 },
92
+ "current_amount": { "label": "Current", "fieldTypeCd": "number", "baseDatatypeCd": "number", "width": 120 },
93
+ "total": { "label": "Total", "fieldTypeCd": "number", "baseDatatypeCd": "number", "width": 130 }
141
94
  }
142
95
  }
143
96
  ```
144
97
 
145
98
  Key differences from entity datasets:
146
- - `datasetType: "S"` (instead of default entity query)
147
- - `sp` — the stored procedure function name (without schema uses the module's schema)
148
- - `columnsDef` — **required** — explicitly defines columns since the platform can't infer them from entity metadata
149
- - Params can be defined per-dataset (dataset-level) or per-report (report-level)
99
+ - `datasetType: "S"`.
100
+ - `spCd` — the stored-procedure code (the function name **without** the schema prefix; resolved to `sp_id` at install). (**Not** `sp` or `procedureName`.)
101
+ - `columnsDef` — **required** — the platform can't infer columns from a function.
102
+ - Params can be per-dataset or per-report.
103
+
104
+ **Multi-result-set SPs** map extra datasets to the same function via `parentDatasetCd` (the dataset that owns the SP call) + `parentRef` (the named refcursor):
105
+
106
+ ```json
107
+ "datasets": {
108
+ "summary": { "caption": "Summary", "datasetType": "S", "spCd": "rpt_department_overview", "columnsDef": { } },
109
+ "by_role": { "caption": "By Role", "datasetType": "S", "parentDatasetCd": "summary", "parentRef": "employee_breakdown", "columnsDef": { } }
110
+ }
111
+ ```
150
112
 
151
113
  ### The PostgreSQL function
152
114
 
@@ -159,240 +121,154 @@ CREATE OR REPLACE FUNCTION crm.rpt_ar_aging(
159
121
  p_as_of_date date DEFAULT NULL, -- User parameter
160
122
  p_customer_id bigint DEFAULT NULL -- User parameter (optional)
161
123
  )
162
- RETURNS TABLE (
163
- customer_name text,
164
- current_amount numeric,
165
- days_30 numeric,
166
- days_60 numeric,
167
- days_90 numeric,
168
- over_90 numeric,
169
- total numeric
170
- )
124
+ RETURNS TABLE ( customer_name text, current_amount numeric, total numeric )
171
125
  LANGUAGE sql STABLE
172
126
  AS $$
173
- SELECT
174
- c.account_name AS customer_name,
175
- SUM(CASE WHEN age <= 30 THEN i.amount_due ELSE 0 END) AS current_amount,
176
- SUM(CASE WHEN age BETWEEN 31 AND 60 THEN i.amount_due ELSE 0 END) AS days_30,
177
- -- ... more columns ...
178
- SUM(i.amount_due) AS total
179
- FROM fin.invoice i
180
- JOIN crm.account c ON c.account_id = i.customer_id
181
- WHERE i.status != 'Paid'
182
- AND (p_customer_id IS NULL OR i.customer_id = p_customer_id)
183
- GROUP BY c.account_name
184
- ORDER BY total DESC;
127
+ SELECT c.account_name, SUM(...) , SUM(i.amount_due)
128
+ FROM fin.invoice i JOIN crm.account c ON c.account_id = i.customer_id
129
+ WHERE i.status <> 'Paid' AND (p_customer_id IS NULL OR i.customer_id = p_customer_id)
130
+ GROUP BY c.account_name ORDER BY 3 DESC;
185
131
  $$;
186
132
  ```
187
133
 
188
134
  **Rules for SP functions:**
189
- - First two params are **always** `p_folder_uid uuid` and `p_user_id bigint` — injected by the platform
190
- - User params come after, with `DEFAULT NULL` for optional ones
191
- - Use `RETURNS TABLE (...)` for single result sets
192
- - Use `RETURNS SETOF refcursor` for multiple result sets (advanced)
193
- - Use the module's schema prefix: `crm.rpt_*`, `fin.rpt_*`, etc.
194
- - Use `STABLE` volatility (read-only, enables PostgreSQL optimization)
195
- - **Security is your responsibility** — filter by `p_folder_uid` and `p_user_id` where needed
196
-
197
- ### Multi-result-set SPs
198
-
199
- For reports with multiple related datasets from one query:
200
-
201
- ```json
202
- "datasets": {
203
- "summary": {
204
- "caption": "Department Summary",
205
- "datasetType": "S",
206
- "sp": "rpt_department_overview",
207
- "spCursor": "department_summary",
208
- "columnsDef": { /* ... */ }
209
- },
210
- "by_role": {
211
- "caption": "By Role",
212
- "datasetType": "S",
213
- "sp": "rpt_department_overview",
214
- "spCursor": "employee_breakdown",
215
- "columnsDef": { /* ... */ }
216
- }
217
- }
218
- ```
219
-
220
- Multiple datasets share the same `sp` (function called once). `spCursor` maps each dataset to a named refcursor returned by the function.
135
+ - First two params are **always** `p_folder_uid uuid` and `p_user_id bigint` — injected by the platform.
136
+ - User params come after, `DEFAULT NULL` for optional ones (order matches `params` declaration order).
137
+ - `RETURNS TABLE (...)` for a single set; `RETURNS SETOF refcursor` for multi-set (mapped via `parentRef`).
138
+ - Use the module's schema prefix (`crm.rpt_*`), `STABLE` volatility, and filter by `p_folder_uid` / `p_user_id` where needed — **security is your responsibility**.
221
139
 
222
140
  ## Parameters
223
141
 
224
- Parameters prompt the user for input before running the report (or action). Both reports and actions use the same parameter system.
225
-
226
- ### Report-level parameters
227
-
228
- Defined in the report's `params` object:
142
+ Parameters prompt the user before running the report. Declare them per-report (`parameters`) or per-dataset (`params`).
229
143
 
230
144
  ```json
231
- "params": {
232
- "start_date": {
233
- "fieldTypeCd": "date",
234
- "label": "Start Date",
235
- "required": true,
236
- "default": "=STARTMONTH()"
237
- },
238
- "end_date": {
239
- "fieldTypeCd": "date",
240
- "label": "End Date",
241
- "required": true,
242
- "default": "=NOW()"
243
- },
244
- "region": {
245
- "fieldTypeCd": "dropdown",
246
- "label": "Region",
247
- "required": false,
248
- "params": {
249
- "options": ["All", "North", "South", "East", "West"]
250
- }
251
- },
252
- "customer": {
253
- "fieldTypeCd": "lookup",
254
- "label": "Customer",
255
- "required": false,
256
- "params": { "entityCd": "account" }
257
- }
145
+ "parameters": {
146
+ "start_date": { "fieldTypeCd": "date", "label": "Start Date", "required": true, "default": "=STARTMONTH()" },
147
+ "region": { "fieldTypeCd": "dropdown", "label": "Region", "params": { "options": ["All", "North", "South"] } },
148
+ "customer": { "fieldTypeCd": "lookup", "label": "Customer", "params": { "entityCd": "account" } }
258
149
  }
259
150
  ```
260
151
 
261
- ### Parameter properties
262
-
263
152
  | Property | Description |
264
153
  |---|---|
265
- | `fieldTypeCd` | UI control type: `date`, `datetime`, `text`, `number`, `dropdown`, `lookup`, `user`, `checkbox` |
154
+ | `fieldTypeCd` | Control type: `date`, `datetime`, `text`, `number`, `dropdown`, `lookup`, `user`, `checkbox` |
266
155
  | `label` | Display label in the parameter dialog |
267
156
  | `required` | Whether the user must fill this before running |
268
- | `default` | Default value plain value or formula with `=` prefix (e.g. `"=NOW()"`, `"=STARTMONTH()"`, `"=TODAY()"`) |
269
- | `params` | Additional config — `options` for dropdowns, `entityCd` for lookups |
157
+ | `default` | Plain value or `=`-prefixed formula (`"=NOW()"`, `"=STARTMONTH()"`, `"=TODAY()"`) |
158
+ | `params` | Extra config — `options` for dropdowns, `entityCd` for lookups |
159
+ | `orderNum` | Display order in the parameter form (falls back to declaration order) |
160
+
161
+ Reference a parameter with `@param_code` inside a query filter value:
162
+
163
+ ```json
164
+ "filter": { "g": "and", "i": [ { "c": "created_date", "o": "grEq", "v": "@start_date" } ] }
165
+ ```
166
+
167
+ SP params are passed positionally after the two required system params, in `params` declaration order.
270
168
 
271
- ### Using parameters in entity query filters
169
+ ## Layout
272
170
 
273
- Reference parameters with `@param_code` in filter values:
171
+ `layout` is an **object** — `{ "panels": [ ... ] }` (not a bare array). Each panel binds a `vizType` to a `datasetCd`:
274
172
 
275
173
  ```json
276
- "filter": {
277
- "g": "and",
278
- "i": [
279
- { "c": "created_date", "o": ">=", "v": "@start_date" },
280
- { "c": "created_date", "o": "<=", "v": "@end_date" }
174
+ "layout": {
175
+ "panels": [
176
+ { "vizType": "chart", "datasetCd": "pipeline", "title": "By Stage", "config": { "chartType": "bar", "categoryCol": "stage", "valueCol": "amount", "agg": "sum", "chartSize": "l" } },
177
+ { "vizType": "kpi", "datasetCd": "pipeline", "title": "Pipeline KPIs", "config": { "metrics": [ { "column": "amount", "agg": "sum", "label": "Open Pipeline" } ] } },
178
+ { "vizType": "table", "datasetCd": "pipeline", "title": "Detail" }
281
179
  ]
282
180
  }
283
181
  ```
284
182
 
285
- ### Using parameters in SP datasets
183
+ ### Visualization types
286
184
 
287
- Parameters are passed to the PostgreSQL function as positional arguments (after the two required system params). The order matches `params` declaration order in the dataset definition.
185
+ The panel `vizType` is one of `table` / `chart` / `kpi` / `pivot` (also `tree`, `markdown`). **Chart type is set via `config.chartType`** — the panel `vizType` is always `"chart"` for any chart.
288
186
 
289
- ### Action parameters (DSL)
187
+ | `vizType` | Description | Config |
188
+ |---|---|---|
189
+ | `table` | Tabular data with sort/filter | optional `groupRules`, `aggregations`, `colorRules` |
190
+ | `chart` | Any chart — kind chosen by `config.chartType` | see below |
191
+ | `kpi` | One or more metric cards | `{ metrics: [ ... ] }` — see below |
192
+ | `pivot` | Pivot table | `rowFields`, `columnFields`, `values` |
290
193
 
291
- Actions declare parameters in the `params:` block of the DSL file:
194
+ **`config.chartType`** `bar` · `horizontalBar` · `stackedBar` · `combo` · `line` · `area` · `pie` · `doughnut` · `scatter` · `bubble` · `funnel` · `heatmap`.
292
195
 
293
- ```
294
- params:
295
- new_stage: dropdown required "New Stage"
296
- note: textarea "Note"
297
- amount: number required "Amount"
298
- ```
196
+ Chart config: `{ chartType, categoryCol, valueCol, agg, seriesCol?, sizeCol?, chartSize? ('sm'|'m'|'l'|'xl'), clickAction?, showTrend?, series? }`. `agg` ∈ `sum|avg|min|max|count`.
299
197
 
300
- Access in execute block: `params[param_name]`
198
+ ### KPI config (metrics)
301
199
 
302
- See `references/action-dsl.md` for full syntax.
200
+ `config.metrics` is an array; each metric is one of two modes:
303
201
 
304
- ### Named parameter sets (saved combos)
202
+ **Aggregation metric** one column reduced by one aggregation:
305
203
 
306
- Users can save frequently-used parameter combinations for quick reuse. These are stored per-report or per-action and appear in a dropdown at the top of the parameter dialog. Defined at runtime (not in the module package).
204
+ ```json
205
+ { "column": "amount", "agg": "sum", "display": "value", "label": "Open Pipeline" }
206
+ ```
307
207
 
308
- ### Cross-parameter validation
208
+ Optional: `display` (`value|gauge|progress|sparkline|icon`), `label`, `target`/`min`/`max`, `icon`, `sparklineDimension`.
309
209
 
310
- Parameters can have cross-validation rules (e.g. `end_date >= start_date`). This is configured via a `validate` formula on the parameter set. For module packages, validation is typically handled by sensible defaults rather than explicit cross-validation formulas.
210
+ **Formula metric** an expression over named aggregation *inputs* (ratios, derived numbers):
311
211
 
312
- ## Layout
212
+ ```json
213
+ {
214
+ "label": "Avg Deal Size",
215
+ "formula": "[total] / [n]",
216
+ "inputs": [
217
+ { "alias": "total", "column": "amount", "agg": "sum" },
218
+ { "alias": "n", "column": "amount", "agg": "count" }
219
+ ],
220
+ "format": { "style": "number", "decimals": 0 }
221
+ }
222
+ ```
223
+
224
+ - `formula` references each input by `[alias]`. Missing alias → `0`; non-finite (÷0, NaN) → blank.
225
+ - `format.style` ∈ `number|percent`; `decimals` sets fraction digits. **Omit `format` (Auto)** to inherit the *first input column's own formatter* — a money formula then reads as money.
226
+
227
+ ### Cross-source metrics & overlay series
313
228
 
314
- The `layout` array defines how datasets are visualized:
229
+ A report already loads **multiple datasets**, so a KPI formula input or a chart overlay series can aggregate over a **sibling** dataset — reference it by its dataset code via `source` (omit = the panel's own `datasetCd`).
230
+
231
+ **Cross-dataset KPI** (pipeline vs leads):
315
232
 
316
233
  ```json
317
- "layout": [
318
- {
319
- "vizType": "bar",
320
- "datasetCd": "ds_pipeline",
321
- "title": "Pipeline by Stage",
322
- "config": {
323
- "categoryCol": "stage",
324
- "valueCol": "total_value",
325
- "agg": "sum",
326
- "chartSize": "l"
327
- }
328
- },
329
- {
330
- "vizType": "grid",
331
- "datasetCd": "ds_pipeline",
332
- "title": "Detail"
333
- },
334
- {
335
- "vizType": "kpi",
336
- "datasetCd": "ds_pipeline",
337
- "title": "Total Value",
338
- "config": {
339
- "valueCol": "total_value",
340
- "format": "currency"
341
- }
342
- },
343
- {
344
- "vizType": "pie",
345
- "datasetCd": "ds_pipeline",
346
- "title": "Distribution",
347
- "config": {
348
- "categoryCol": "stage",
349
- "valueCol": "opp_count"
350
- }
351
- }
352
- ]
234
+ { "vizType": "kpi", "datasetCd": "deals", "config": { "metrics": [
235
+ { "label": "Total Funnel Value", "formula": "[pipeline] + [leadval]", "inputs": [
236
+ { "alias": "pipeline", "column": "amount", "agg": "sum" },
237
+ { "alias": "leadval", "column": "estimated_value", "agg": "sum", "source": "leads" }
238
+ ] }
239
+ ] } }
353
240
  ```
354
241
 
355
- ### Visualization types
242
+ **Cross-source chart overlay** — `config.series` (a single object **or** an array) adds series aggregated from other datasets, aligned on a shared category axis. Supported on `bar`/`horizontalBar`/`line`/`area`. Categories are outer-joined; a missing bucket fills `0` (bar) or gaps with `null` (line/area). Keep each series' `categoryCol` matching the primary axis unless a different axis is intended.
356
243
 
357
- | `vizType` | Description | Config |
358
- |---|---|---|
359
- | `grid` | Tabular data with sort/filter | — |
360
- | `bar` | Vertical bar chart | `categoryCol`, `valueCol`, `agg` |
361
- | `bar-stacked` | Stacked vertical bar | `categoryCol`, `valueCol`, `stackCol`, `agg` |
362
- | `h-bar` | Horizontal bar | Same as `bar` |
363
- | `line` | Line chart | `categoryCol`, `valueCol`, `agg` |
364
- | `area` | Area chart | Same as `line` |
365
- | `pie` | Pie chart | `categoryCol`, `valueCol` |
366
- | `doughnut` | Doughnut chart | Same as `pie` |
367
- | `scatter` | Scatter plot | `xCol`, `yCol` |
368
- | `combo` | Combined bar + line | `categoryCol`, `barCol`, `lineCol` |
369
- | `kpi` | Single value card | `valueCol`, `format` (optional: gauge, progress, sparkline, icon variants) |
370
- | `pivot` | Pivot table | `rowCols`, `colCols`, `valueCols`, `aggFunc` |
244
+ ```json
245
+ { "vizType": "chart", "datasetCd": "deals", "config": {
246
+ "chartType": "bar", "categoryCol": "lead_source", "valueCol": "amount", "agg": "sum",
247
+ "series": { "source": "leads", "categoryCol": "lead_source", "valueCol": "estimated_value", "agg": "sum", "label": "Lead Value" }
248
+ } }
249
+ ```
371
250
 
372
- ### Chart size
251
+ Dashboard KPI/chart *tiles* get the same support via a `sources` map in the tile config (authored with "Add source" in the tile dialog) — not part of a module's `ui/reports.json`.
373
252
 
374
- Chart visualizations support an optional `config.chartSize` property to control the default display height.
253
+ ### Chart size
375
254
 
376
- | Field | Type | Allowed values | Description |
377
- |---|---|---|---|
378
- | `config.chartSize` | string | `sm`, `m`, `l`, `xl` | Optional size preset for chart height. When omitted, the default is `m`. |
255
+ `config.chartSize` ∈ `sm | m | l | xl` controls the default chart height (`m` when omitted).
379
256
 
380
257
  ## Grants
381
258
 
382
- Report access is granted via the `E` right:
259
+ Grant report access with the `E` right:
383
260
 
384
261
  ```json
385
- "rights": {
386
- "report.sales_pipeline": "E"
387
- }
262
+ "rights": { "report.sales_pipeline": "E" }
388
263
  ```
389
264
 
390
265
  ## Common mistakes
391
266
 
392
- - Mixing `entityCode` + SP fields (`datasetType`, `sp`, `columnsDef`) in the same dataset pick one source type.
393
- - Forgetting `columnsDef` on SP datasets **required** (platform can't infer columns from a function).
394
- - Forgetting to grant `E` access in at least one role report becomes invisible.
395
- - Forgetting `p_folder_uid` and `p_user_id` as first two SP function params call will fail.
396
- - Using `panels` instead of `layout` for the visualization array use `layout`.
397
- - Using `widget` instead of `vizType`use `vizType`.
267
+ - Using a bare `"layout": [ ... ]` array — `layout` is an **object**: `"layout": { "panels": [ ... ] }`.
268
+ - Setting the chart kind as `vizType` (`"vizType": "bar"`)the panel `vizType` is `"chart"`; the kind goes in `config.chartType`.
269
+ - Using `entityCode` / `groupBy` / `aggregations` on a datasetuse `query.entityCd` + `query.columns`, and aggregate in the viz (`agg`, `metrics`).
270
+ - Using `sp` / `procedureName` for an SP datasetthe field is `spCd`.
271
+ - Forgetting `columnsDef` on an SP dataset**required**.
272
+ - Forgetting to grant `E` on the report in at least one role it becomes invisible.
273
+ - Forgetting `p_folder_uid` / `p_user_id` as the first two SP function params — the call fails.
398
274
  - Referencing a parameter as `$param` — use `@param_code` in filters.