toga-ai 1.0.1070 → 1.0.1071
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.
|
@@ -6,11 +6,13 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [apeterson]
|
|
9
|
+
updated: 2026-10-17
|
|
10
|
+
owners: [apeterson, jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/TableView.php
|
|
13
13
|
- _underscore/Model/Compass/SalesOrder.php
|
|
14
|
+
- _underscore/Model/Client/Certification.php
|
|
15
|
+
- _underscore/Model/Client/UserCertification.php
|
|
14
16
|
- _underscore/Model.php
|
|
15
17
|
related:
|
|
16
18
|
- sales-order-status-filter-surface.md
|
|
@@ -34,7 +36,10 @@ Before this, `filterOptions` in table meta was filled in **only** for `TYPE__STA
|
|
|
34
36
|
column whose options are **live data** (e.g. the list of active Admin users): seeding a surface
|
|
35
37
|
element per user would go stale constantly.
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
Consumers: Compass **Assigned Admin** (live data, below); `_Model_Client_Certification::_complianceStateFilterOptions()`
|
|
40
|
+
and `_Model_Client_UserCertification::_statusFilterOptions()` (fixed enum list: loop the model's
|
|
41
|
+
state constant, `slug` = `name` = the value; the front end labels it from language data). Use the
|
|
42
|
+
enum shape for any status-like calc column.
|
|
38
43
|
|
|
39
44
|
## How it works
|
|
40
45
|
|
|
@@ -56,7 +61,7 @@ an empty dropdown. Same contract as `getStatusFilterOptions()`.
|
|
|
56
61
|
throws when a `FIELD_SQL` field has no static of the same name — that is why `_assignedAdmin()`
|
|
57
62
|
exists. The seam just adds a `FilterOptions` suffix to the same name-derived rule.
|
|
58
63
|
|
|
59
|
-
It is a **no-op for
|
|
64
|
+
It is a **no-op** for any column whose model declares no `<field>FilterOptions` static.
|
|
60
65
|
|
|
61
66
|
### The Compass example
|
|
62
67
|
|
|
@@ -161,10 +166,11 @@ generic version at roughly **4–5** such columns, driven by real examples.
|
|
|
161
166
|
the same pattern `_assignedManager()` already uses.
|
|
162
167
|
- **Matching a role by its human-readable `name` is fragile** (flagged by `sql-reviewer`): a client
|
|
163
168
|
renaming the `Admin` role silently empties the list. A stable role id would be sturdier.
|
|
164
|
-
- **⚠
|
|
165
|
-
`
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
- **⚠ Filter with the BARE calc name** (`_complianceState:eq:AT_RISK`) — it is inlined into
|
|
170
|
+
`WHERE`. The table-prefixed form (`Table._x`) goes to `HAVING` and returns 500 / `EO-1`. Mechanism:
|
|
171
|
+
[V2 REST query contract](../../api2/features/v2-rest-query-contract.md). Either way the expression
|
|
172
|
+
(here the `_assignedAdmin` correlated subquery) is evaluated **per row, before LIMIT** — and again
|
|
173
|
+
for the pagination count query. On a large
|
|
168
174
|
Compass `SalesOrders` table this can be slow, and it will not fail loudly (api2's Apache gateway
|
|
169
175
|
timeout is 1800s). This is pre-existing behaviour of filtering **any** calculated column, not new
|
|
170
176
|
here, and it was **not** measured with `EXPLAIN` at production size. The cheaper target would be
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-17
|
|
10
10
|
owners: [apeterson, bala, jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/TableView.php
|
|
@@ -140,6 +140,7 @@ So the visible failure is a broken SQL query while the actual defect is one miss
|
|
|
140
140
|
- **Flag columns are NULLable** — guard flag migrations with `COALESCE(col, 0) = 0`, never `col = 0`, or the UPDATE matches 0 rows and looks like caching.
|
|
141
141
|
- **`type`/`precision` are canonical (Core-wide), not per-view.** Editing `Core.RecordFields` changes every view/client that shows the field; there is no per-view type override. `isCopyable`, `isSortable`, `isFilterable`, `isGroupable`, `isVisible` are the per-view knobs (on `TableViewFields`).
|
|
142
142
|
- **`TableView::meta()` contains a hand-written `SELECT` that names `TableViewFields` columns explicitly.** So a new column on that table is required by **two** mechanisms — the model property *and* that query — and a grep for the property alone under-reports what a missing column breaks.
|
|
143
|
+
- **⚠ Type `SELECT` only on a JOINED FK column.** `meta()`'s `TYPE__SELECT` branch reads `$tableViewJoins[$tableAlias]->record` (`TableView.php` ~L265-272); a main-table column has no join alias, so it breaks at runtime. For an enum column on the main table, expose a calc label field (e.g. `_status`) with a `<field>FilterOptions` static — see [calculated-column filter options](../../_underscore/features/calculated-column-filter-options.md).
|
|
143
144
|
- **`isGroupable` has no migration** — omit it from new TableView migrations (fails on prod/beta) until the fan-out migration lands. Composite unique keys read as unconstrained in `information_schema.COLUMNS`; use `information_schema.STATISTICS` / `SHOW INDEX`.
|
|
144
145
|
|
|
145
146
|
## Related
|
|
@@ -151,4 +152,5 @@ So the visible failure is a broken SQL query while the actual defect is one miss
|
|
|
151
152
|
- [TableView joins](../../_underscore/features/tableview-joins.md)
|
|
152
153
|
|
|
153
154
|
## Change history
|
|
155
|
+
- 2026-10-17 — `SELECT` type is safe only on a joined FK column; main-table enum → calc label + FilterOptions. (jcardinal)
|
|
154
156
|
- 2026-09-23 — Recorded that `Core.RecordFields.type` drives display/filter only and never sorting; that `NUMBER` on a text column blanks non-numeric rows (Compass `kit 205-207` bundles) while `BOOLEAN` fixes genuinely blank flag columns; and that the flag columns are NULLable so a `= 0` migration guard matches nothing. (apeterson)
|
|
@@ -56,6 +56,10 @@ controller.
|
|
|
56
56
|
(`V2.php` ~L3673–3682). **Prefer bare for ascending:** the query string is parsed *without*
|
|
57
57
|
urldecoding (see Encoding rules), so a client that percent-encodes the `+` as `%2B` sends a
|
|
58
58
|
field literally named `%2Bfield` and the sort is silently dropped. Bare has no such failure mode.
|
|
59
|
+
- **Multi-sort = comma list** (`sort=-dateIssued,uuid`; `parseOptionsSort` ~L10888 explodes on `,`;
|
|
60
|
+
a blox `sort: [...]` array is sent comma-joined). **Offset paging (`page`) is only stable with a
|
|
61
|
+
unique last key** — add `uuid` as the tie-breaker, or rows with equal sort values shuffle between
|
|
62
|
+
pages.
|
|
59
63
|
- **A LIST select is `SELECT SQL_CALC_FOUND_ROWS DISTINCT <table>.<primaryKey>`** (`V2.php:3884`;
|
|
60
64
|
the aggregate-fields path is the one exception). Two consequences: (a) join fan-out collapses to
|
|
61
65
|
one row per record, so **duplicate child rows are invisible in the response** — a duplicate join
|
|
@@ -382,9 +386,10 @@ Mechanism, read from `api2/Component/Api/V2/V2.php`:
|
|
|
382
386
|
1. `buildWhereExpressionsFromOptions` (~L6953) **deliberately does not prepend the default table**
|
|
383
387
|
to a field starting with `_` — its own comment says *"add the default table if not specified and
|
|
384
388
|
is not a calculated field."*
|
|
385
|
-
2. `
|
|
386
|
-
|
|
387
|
-
|
|
389
|
+
2. `validateWhereFieldReference` (~L8704-8741) accepts a bare calc name on the primary table.
|
|
390
|
+
3. `buildWhereClauseFromWhereExpressions` (~L9225-9234) sees the leading `_` and **inlines the field's
|
|
391
|
+
own SQL expression** straight into the `WHERE` clause. That is the working path.
|
|
392
|
+
4. `splitWhereExpressionsIntoWhereHaving` (~L11286) routes to `HAVING` **only when the field contains
|
|
388
393
|
`._`** — i.e. only the table-**prefixed** form. That form targets a joined-table select alias
|
|
389
394
|
(`PurchaseOrderItems__qtyAvailable`, built ~L3984) which **does not exist for the main table**,
|
|
390
395
|
and ~L4116 also appends the `HAVING` to the `$sqlProhibited` COUNT query, which has no such alias
|
|
@@ -411,9 +416,11 @@ production — `toga2-supply/src/api/toga.ts` L906-919 (`_status` OR group) and
|
|
|
411
416
|
**Zero** occurrences of the table-prefixed form exist in any repo — nobody has ever used it
|
|
412
417
|
successfully.
|
|
413
418
|
|
|
414
|
-
>
|
|
415
|
-
>
|
|
416
|
-
>
|
|
419
|
+
> **`sort` on a calculated field also works** (bare name, calc branch of `ORDER BY` above).
|
|
420
|
+
> **`group=_status` fails.** `where` still never prunes a nested child array (see below).
|
|
421
|
+
> A TableView column over a calc field can be `isFilterable` — a code comment saying "calc fields
|
|
422
|
+
> are not filterable" records a choice, not an engine limit. Dropdown options:
|
|
423
|
+
> [calculated-column filter options](../../_underscore/features/calculated-column-filter-options.md).
|
|
417
424
|
|
|
418
425
|
#### ⚠ `in` instead of an OR-chain of `contains` shortens the SQL - it does NOT make the filter fast
|
|
419
426
|
|
|
@@ -618,6 +625,7 @@ If you need the behaviour, loop uuids client-side. Point 3 is the durable, non-o
|
|
|
618
625
|
[V2 request logging](request-logging.md).
|
|
619
626
|
|
|
620
627
|
## Change history
|
|
628
|
+
- 2026-10-17 — Multi-sort comma list + `uuid` tie-breaker for stable paging; sort on calc works; refreshed where-path line refs. (jcardinal)
|
|
621
629
|
- 2026-10-17 — POST create = 201, PUT/DELETE = 200 (TRUE-82889). (jcardinal)
|
|
622
630
|
- 2026-10-15 — Security: `where` on an unreadable field still filters under default `identifier_acl_mode=log` (hash guessing); dotted FK path in `where` = `EV-8`, use a join alias (TRUE-82836). Folded older history. (jcardinal)
|
|
623
631
|
- 2026-09-24 — `depth`/`calcDepth` ignored inside the base64 `_` blob; field three levels under an inherent child dropped (TRUE-82138). (apeterson)
|
package/package.json
CHANGED