Repository navigation
Commit b824a66
fix(fields): a lookup whose record read is refused draws the restricted-record placeholder, not a linked raw id (#29708)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbXsaGxtGer5XYc7Y7N6hJir)_
Fixes #29641
Clause-②: no
Metadata-protocol: none
Claim: 6592434215
## 维护者速读
- 改了什么:lookup 字段单元格在读取被引用记录时,如果服务端以「无权限」拒绝(403 或
`PERMISSION_DENIED`),现在显示「受限记录」占位:不显示原始
id、不带链接,与「记录不存在」(404)同一外观;拒绝结果会缓存,同一记录只读一次。网络错误、5xx、401
等其他失败保持原来的「未解析」外观(id + 问号)。
- 为什么改:目标对象的读权限拒绝读者时,服务端对记录读取回 403 而不是 404,单元格把它当成读取失败,画出带链接的原始
id,点进去是一个拒绝访问的页面,看起来像坏数据。
- 风险与回滚:只改 `useLookupName` 的 catch 分支,判定复用 `@object-ui/react` 已有的
`classifyLoadError`(`forbidden`),不改数据适配器、不改 `findOne` 的返回值;404
占位、可读记录、在途读取的外观全部不变。回滚即 revert 本 PR。
- 先看:`ui/packages/fields/src/index.tsx` 的 `useLookupName` catch 分支与
`refused` 缓存状态;新测试 `lookupCell.forbiddenRead-29641.test.tsx`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/fields/src/__tests__/lookupCell.forbiddenRead-29641.test.tsx`,预期
12 个用例全部通过。
- 需要你做的:无,席位落地。
## What changes
A lookup cell names its referenced record by reading it as the viewer
(`useLookupName`, `ui/packages/fields/src/index.tsx`). objectui#13335
drew a record the read answered "not found" (`findOne` answers `null`,
the adapter's 404) as "Restricted record", with no id and no link. When
the target object's read gate refuses the viewer, the server answers the
same read with `403 PERMISSION_DENIED`, which the adapter throws, and
the hook caught every throw as a failure: the cell drew the raw id
beside a question mark, inside a link to a record page that refuses the
viewer.
| The viewer's read of the referenced record | Before | Now |
|---|---|---|
| answers `null` (404, hidden by sharing or missing) | Restricted
record, no link | unchanged |
| throws a refusal: status 403 (`httpStatus`, `status` or `statusCode`)
or code `PERMISSION_DENIED` / `FORBIDDEN` | the id with a question mark,
linked | Restricted record, no id, no link; cached, read once |
| throws anything else (network, 5xx, 401) | the id with a question mark
| unchanged |
- The refusal is read through `@object-ui/react`'s `classifyLoadError`
(`forbidden`), which `@object-ui/fields` already depends on and imports;
no new predicate and no message matching. The lookup cache gains a
`refused` state, which the hook answers as `absent`, so
`LookupCellRenderer` is unchanged apart from a comment.
- Measured live before the edit (`pnpm dev:crm`, a member refused
`rnv_payroll`): the adapter's `findOne` throws a plain `Error` whose own
members are `httpStatus: 403`, `code: 'PERMISSION_DENIED'` and `details`
(`error`, `code`, `object`), with no `status` / `statusCode`;
`classifyLoadError` answers `forbidden` on it. A missing id under the
same object answers the same 403, so the placeholder discloses nothing a
not-found would not.
- `user` cells: unchanged. `UserCellRenderer` never reads the referenced
person (a bare id is `UnresolvedUserReference`), so it has no read
answer to classify.
- Not changed: `@object-ui/data-objectstack` and what `findOne` returns.
## Tests
-
`ui/packages/fields/src/__tests__/lookupCell.forbiddenRead-29641.test.tsx`
(new, 12): the measured 403 draws the placeholder with no id in text or
attributes, no anchor, one `findOne` and no `find`; the four single
spellings (`httpStatus`, `status`, `statusCode` 403, code
`PERMISSION_DENIED`) each draw it; the refusal is cached (a second cell
for the same id draws it with no read) and a 502 on another id
afterwards keeps the unresolved face; in a multi-value cell only the
first, read entry is restricted. CONTROLS: a `null` answer keeps the
placeholder; a 500 with a code, a 503, a network `TypeError` and a 401
each keep the unresolved face with their id.
- Unchanged and green: `lookupCell.restrictedRecord-13335.test.tsx`,
`lookupCell.referenceNameVisible-29462.test.tsx`, the other lookupCell
files and `DetailSection.referenceNameVisible-29462.test.tsx`: 12 files,
97 tests.
- Ablations (`scripts/ablation-replace.mjs`, anchor 1 to 0, restored to
the HEAD blob, `git diff HEAD` empty), run on the new file plus the
#13335 file: A1 the catch records every throw as `err`: 7 failed / 14
passed; A2 the `refused` entry no longer answers `absent`: 7 failed / 14
passed. The 7 are exactly the refusal cases; every control stays green.
## Acceptance notes
- Seat additions honoured: the cache pin (refusal cached like a
not-found, no cross-key bleed to a later transient failure) is the third
test above.
- The cell's `useRefObjectSchema` reads the target's object definition
(`/meta/object/rnv_payroll`) before and after this change alike; it is
not part of the record read and is left as it is.
- Local gates on the final head `214f2289` (main merged twice; none of
the merged commits touches `ui/packages/fields`): `dispatch-gates
--commands` derives 35; all 35 exit 0 (33 in an unlocked loop,
`check:type-check-debt` and `pnpm --dir ui exec vitest run scripts/`
(170 files) under the verify lock); `--ran`: 35 derived, 35 run, 0
NOT-MEASURED. The same 35 also exit 0 on the earlier head `647dc13a`.
`ui/scripts/check-spec-symbol-derivation.mjs` exit 0 (PR #29623 edits
the same file's `ReferenceNameOnlyValue`; this diff stays clear of it).
`turbo run type-check --filter=@object-ui/fields` exit 0 (48 tasks).
`@object-ui/fields` suite: 296 files passed, 1 skipped, 4449 tests.
eslint on the two touched ui files: 0 errors. The repo-wide sweep is
CI's.
## Verification
<details><summary>Live, <code>pnpm dev:crm -- --fresh</code> plus the
source Console (Vite), a member refused the payroll object</summary>
- Setup is the #29462 recipe: a scratch package `com.example.rnv` with
`rnv_payroll` (private, admin-owned) and `rnv_expense` (`public_read`)
carrying `Payroll (no key)`, a lookup without `referenceNameVisible`; a
member granted read on `rnv_expense` only, impersonated. `GET
/api/v1/data/rnv_payroll/ID` as the member: 403 `PERMISSION_DENIED`.
- Before (main `fc558923`): list and record page each draw `Payroll (no
key)` as the unresolved face with the id, inside a link to `rnv_payroll`
(1 anchor).
- After (this branch): list and record page each draw "Restricted
record" for it, not inside a link, and no anchor to `rnv_payroll`
remains. The `referenceNameVisible` fields still draw their served names
as text. Screenshots and the per-page readings are in the project folder
(`issue-29641/shots/`), not on GitHub.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jRxjiKm4UH4VWn45pF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2ea0fc8 commit b824a66
3 files changed
Lines changed: 219 additions & 6 deletions
File tree
- .changeset
- ui/packages/fields/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 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 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 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 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
| 47 | + | |
46 | 48 | | |
47 | 49 | | |
48 | 50 | | |
| |||
244 | 246 | | |
245 | 247 | | |
246 | 248 | | |
247 | | - | |
248 | | - | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
249 | 258 | | |
250 | 259 | | |
251 | 260 | | |
| |||
324 | 333 | | |
325 | 334 | | |
326 | 335 | | |
327 | | - | |
328 | | - | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
329 | 343 | | |
330 | 344 | | |
331 | 345 | | |
| |||
335 | 349 | | |
336 | 350 | | |
337 | 351 | | |
| 352 | + | |
338 | 353 | | |
339 | 354 | | |
340 | 355 | | |
| |||
2727 | 2742 | | |
2728 | 2743 | | |
2729 | 2744 | | |
2730 | | - | |
| 2745 | + | |
2731 | 2746 | | |
2732 | 2747 | | |
2733 | 2748 | | |
| |||
0 commit comments