Repository navigation
Commit f8195bd
fix(drill): the list URL carries a boolean, a set, $ne and the text operators, so Open in list and View all regain an exact destination (#30777)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu1MP9pZ1S7S7wkrGF5w4Znn)_
Fixes #30743
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:对象列表页 `/apps/APP/OBJECT/data` 的 URL 新增六种写法:布尔
`filter[F][bool]=true|false`、`$ne` 与五个文本运算符
`filter[F][ne|contains|notcontains|startswith|endswith|icontains]=V`、集合
`filter[F][in]=A&filter[F][in]=B`(及
`[nin]`)。下钻「在列表中打开」、`drillDown.target: 'navigate'`
和相关列表「查看全部」遇到这些条件时恢复去处。
- 为什么改:#30654 之后,凡是 URL 说不全的下钻都不给出口;按是/否字段分组的图表、`status in (...)`
这类度量范围最常见,全部没了去处。实测(见下):布尔原样写出会读回成字符串 `'true'`,其余写法根本写不出参数。
- 风险与回滚:只放宽「能精确写出」的集合,每个新写法读回后必须与抽屉查询的 AST
节点完全一致(含类型)才放行;`$or`、`$between`、空集合、布尔的 `$ne` 仍然不给出口。旧链接
`filter[F]=true` 含义不变(文本)。回滚即 revert。
-
先看:`ui/packages/app-shell/src/views/drillUrlFilters.ts`(新常量与读写两侧)、`ui/packages/app-shell/src/views/useOpenRecordList.ts`(`exactRecordListFilter`
的接受集与
`isSameComparison`)、`ui/packages/app-shell/src/views/ObjectDataPage.tsx`(筛选标签渲染与「另存为视图」桥接)。
- 两分钟验证:打开
`/apps/APP/OBJECT/data?filter[status][in]=paid&filter[status][in]=sent`,预期列表只含这两种状态,顶部标签显示「In
paid, sent」(中文包为对应译文),点标签上的 ✕ 两个参数一起移除。
- 需要你做的:无,席位落地。
## What changes
Measured first, on `origin/main` `b3098661`, through the real
`toFilterNode` → `parseFilterAST` → `serializeDrillFilterParams` →
`parseUrlFilterTriples` chain, against `convertFiltersToAST` (what the
drill drawer queries):
| condition | drawer node | URL written before | read back before |
|---|---|---|---|
| `{ is_active: true }` | `[is_active, '=', true]` |
`filter[is_active]=true` | `[is_active, '=', 'true']` (a string) |
| `$in` / `$nin` | `[status, 'in', [...]]` | nothing | nothing |
| `$ne` | `[status, '!=', 'void']` | nothing | nothing |
| `$contains`, `$notContains`, `$startsWith`, `$endsWith`, `$icontains`
| `[name, 'contains', 'acme']` … | nothing | nothing |
All 18 probed cases answered `exactRecordListFilter` → `null`. After
this PR every one of them round-trips to the drawer's own node, except
the three that must not (an empty `$in`, a boolean `$ne`), which still
answer `null`.
`ui/packages/app-shell/src/views/drillUrlFilters.ts`
- `BOOLEAN_FILTER` (`[bool]`): the only two spellings are `true` /
`false`, read back as the boolean; anything else in that slot is
dropped, never read as text. A bare boolean in a drill filter is written
there instead of `filter[F]=true`.
- `URL_COMPARE_OPS` / `COMPARE_OP_PARAM`: `ne → '!='` and the five text
operators, suffix → the AST word `convertFiltersToAST` emits.
Prototype-free, like `URL_FILTER_OPS`, which keeps its four pinned
entries.
- `URL_LIST_OPS` / `LIST_OP_PARAM`: `in` / `nin` as a repeated param,
one member per occurrence (no delimiter, so a member may contain a
comma). The read side gathers every occurrence on one field into one
triple at the first occurrence's position. The write side writes the
whole list or nothing (an empty list, an empty member or a boolean
member writes no param), and a later source replaces an earlier list on
the same field, the `set` posture of every other param.
- `URL_TRIPLE_OP_WORD`: AST word → canonical `ViewFilterRule` word, read
by the chips (label key `filterBuilder.operators.WORD`, existing in all
ten packs) and by "Save as view".
- `groupFilterChips`: a negation, text or membership chip carries the
operator label as `textKey` and the comparand as `text` (a set joined
with a comma). `FilterChip.textKey`'s doc now says it may sit beside
`text`. Every existing chip is unchanged.
`ui/packages/app-shell/src/views/useOpenRecordList.ts`
- `exactRecordListFilter` accepts a boolean under equality (bare or
`$eq`), `$ne` with a string or finite number, the text operators with a
non-empty string, and `$in` / `$nin` with a non-empty list of strings or
finite numbers.
- Each condition's proof is stronger: written alone it must read back as
one triple (now: one param NAME, repeated for a set), AND that triple
must equal `convertFiltersToAST(condition)`, field, operator and
comparand, with the one difference the route has always carried (a
finite number reads back as its text). A boolean reading back as
`'true'` fails this check.
`ui/packages/app-shell/src/views/ObjectDataPage.tsx`
- The chip renders `t(textKey) + ' ' + text` when it carries both.
- `TRIPLE_OP_TO_SPEC_ALIAS` spreads `URL_TRIPLE_OP_WORD`, so "Save as
view" folds `!=` to `not_equals`, `nin` to `not_in`, and so on; the
record gate passes.
One changeset: `@object-ui/app-shell` patch.
## Tests
- `drillUrlFilters.test.ts`: a new describe for the six spellings:
write/read for each against `convertFiltersToAST`, only two `[bool]`
spellings, `filter[name]=true` still text, set order and grouping,
whole-list-or-nothing, later set replaces, boolean `$ne` writes nothing,
prototype sweep over the new lookups, chips, prefix delete of a set,
equality-only route still executes none.
- `exactRecordListFilter-25888.test.ts`: the new accepted spellings
round-trip; each equals the drawer node; numbers in a set; a set beside
an equality; a repeated set written once; new refusals (empty set, empty
member, boolean member, two different sets, boolean `$ne`, text operator
with a number, `$between`).
- `useOpenRecordList.placeholderScope-9022.test.tsx`: through the real
host, an `$in` scope and a boolean bucket open the exact URL; a boolean
`$ne` still opens nothing.
- `ObjectDataPage.saveAsViewFilterFold.test.ts`: every new operator
folds to a canonical rule and the `ViewItemSchema` gate passes; the
operator sets are computed from the exported maps.
- `ObjectDataPage.filterChipI18n-9159.test.tsx`: on the real page with
the real packs, `[in]` / `[nin]` / `[ne]` chips draw the zh label beside
the comparand, and the en control differs.
Moved pins (intended, each the behaviour this card asks to change):
- `exactRecordListFilter-25888`: four refusals (set operator, negation,
text operator, boolean comparand) became accepted cases.
- `useOpenRecordList.placeholderScope-9022`: "an `$in` scope offers no
destination" now opens the exact list.
- `ObjectView.urlFilterSuffix-9196`: row 1 used `[contains]` as its
unknown suffix; it now uses `[like]`, and a new row pins `[contains]`
executing on `/data` only while the object route still drops it. Its
non-vacuity battery gains the `/data` contains triple.
## Acceptance notes
- A boolean chip reads `= true` / `= false`: the raw comparand, the
posture every equality chip already has (an option label is not drawn
either).
- A set of numbers reads back as the members' text (`['1', '2']`), the
posture equality and the range bounds have always had; the exactness
check allows exactly that difference and nothing else.
- Still refused, by design: `$or` and every other combinator,
`$between`, an empty set, a set member that is empty or boolean, `$ne`
on a boolean, a text operator with a non-string, two different
comparands or sets in one param.
- The plain object route (`ObjectView`, equality-only, objectui#9196's
boundary) learns none of the new suffixes.
- `ui/packages/i18n/src/locales/*.ts` untouched: the chip labels are the
filter builder's existing `filterBuilder.operators.*` keys.
- Serial: PR #30595 also adds lines to
`ObjectDataPage.filterChipI18n-9159.test.tsx`; this PR appends one
describe at the end of that file. Whichever lands second merges the
other.
- NOT MEASURED: a live browser run of the Console; the chip and
navigation behaviour is covered through the real `ObjectDataPage` render
and the real `useRecordListDrillNavigation` host in the tests above.
## Verification
All readings on head `ed322918`.
<details><summary>Targeted suites: 21 files, 383 tests, all
green</summary>
`pnpm exec vitest run --maxWorkers=2` from `ui/` over
`drillUrlFilters.test.ts`, `exactRecordListFilter-25888`, both
`RelatedRecordActionsBridge` View-all suites (12741, 25888),
`useOpenRecordList.placeholderScope-9022`,
`ObjectView.urlFilterSuffix-9196`,
`ObjectView.filterParamsKeyEncoding-9287`, every `ObjectDataPage.*`
suite, every `drill*` suite in app-shell views, `studio-canvas-preview`,
and plugin-dashboard's `DatasetWidget` and
`ObjectPivotTable.drillFilterComposition-9024`: `Test Files 21 passed
(21)`, `Tests 383 passed (383)`. Before the pin moves the same run read
`7 failed | 339 passed`, the seven moved pins listed above and nothing
else.
</details>
<details><summary>Type-check: clean</summary>
`pnpm --filter @object-ui/app-shell type-check` (both `tsc --noEmit` and
`tsc -p tsconfig.test.json`, after building the
`@object-ui/app-shell^...` closure): 0 `error TS`, `VERDICT command-exit
0`.
</details>
<details><summary>Ablation: one mutation, three reds, restored</summary>
`node scripts/ablation-replace.mjs` on `drillUrlFilters.ts`, anchor
`out.push([field, '=', true])` in the `[bool]` read arm replaced with
the string `'true'` (anchor 1 → 0, blob `35873c7f` → `0120ef72`):
`drillUrlFilters.test.ts` "reads the `[bool]` slot back as the BOOLEAN
the drawer compares with" red, and `exactRecordListFilter-25888` "writes
the spellings objectstack#30743 added" plus "reads each of those back as
the node the drill drawer queries" red (the gate's comparand-type check
refusing the string). `Tests 3 failed | 116 passed`. Restored: blob ==
HEAD, `git diff HEAD` empty.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands` names 35 families for
this change set (10 paths, 642 changed lines, under the 3000 threshold).
Running locally at open time; the readings and the `--ran`
reconciliation are in the os-dev-report comment on #30743. Every family
CI runs is read from this PR's check-runs.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(`packages/spec` untouched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` is CI's run.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the maintainer brief
is the repo's Chinese 速读 block).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgW9yrPaVhdtD84RWcR)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgW9yrPaVhdtD84RWcR
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9bdeed2 commit f8195bd
10 files changed
Lines changed: 609 additions & 33 deletions
File tree
- .changeset
- ui/packages/app-shell/src/views
- __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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
256 | 256 | | |
257 | 257 | | |
258 | 258 | | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
| 33 | + | |
32 | 34 | | |
33 | 35 | | |
34 | 36 | | |
| |||
103 | 105 | | |
104 | 106 | | |
105 | 107 | | |
106 | | - | |
| 108 | + | |
107 | 109 | | |
108 | 110 | | |
109 | 111 | | |
| |||
150 | 152 | | |
151 | 153 | | |
152 | 154 | | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
153 | 182 | | |
154 | 183 | | |
155 | 184 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| 60 | + | |
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
| |||
147 | 148 | | |
148 | 149 | | |
149 | 150 | | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
150 | 154 | | |
151 | 155 | | |
152 | 156 | | |
| |||
171 | 175 | | |
172 | 176 | | |
173 | 177 | | |
174 | | - | |
| 178 | + | |
175 | 179 | | |
176 | 180 | | |
177 | 181 | | |
| |||
675 | 679 | | |
676 | 680 | | |
677 | 681 | | |
678 | | - | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
679 | 685 | | |
680 | 686 | | |
681 | 687 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
82 | 90 | | |
83 | 91 | | |
84 | 92 | | |
| |||
160 | 168 | | |
161 | 169 | | |
162 | 170 | | |
| 171 | + | |
163 | 172 | | |
164 | 173 | | |
165 | 174 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
69 | 70 | | |
70 | 71 | | |
71 | 72 | | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
72 | 145 | | |
73 | 146 | | |
74 | 147 | | |
75 | 148 | | |
76 | 149 | | |
77 | 150 | | |
78 | 151 | | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
83 | 159 | | |
84 | 160 | | |
85 | 161 | | |
| |||
0 commit comments