You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ab12a3b
Browse filesBrowse the repository at this point in the historyBrowse files
fix(app-shell): the datasource delete confirm and its refusal name the objects that still use it, in the console's language (#30442)
domain:ui#5 · os-sales · session_01L6bhgW7fTeU6tAZXwiAJnW
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuQqREozq6kAm9qNDvozztaj)_
Refs #30335
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:设置 →
数据源,删除确认框会列出仍在使用该数据源的对象,并说明在它们还在时删不掉;服务端拒绝删除后,提示改用控制台语言,点名这些对象并说明下一步(删除对象或改到其他数据源)。
- 为什么改:服务端拒绝时只回一句英文和一个数量,页面原样塞进中文外框(「删除 parts_db 失败:Cannot remove
datasource …」),中文管理员看不懂,也不知道是哪些对象挡住了。
- 风险与回滚:只改前端一个页面和 studio 词条(新增 3 行 en/zh);对象读取失败或非 400
失败时保持原样显示服务端原文;回滚即还原本 PR,无数据迁移。
-
先看:`ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.tsx`(`openDeleteConfirm`、`remove`
的 400 分支)、`DatasourceResourcePage.deleteConfirm-13333.test.tsx` 第二个
describe、`ui/packages/i18n/src/namespaces/studio/zh.ts` 的
`engine.datasource.deleteBound*`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.deleteConfirm-13333.test.tsx`,预期
8 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
Card #30335, the **datasource half only**. The storage half
(`SettingsView.tsx`, console locales) stays on the card, serial behind
#30329, so this PR does not close it.
- **The confirm.** Opening a datasource's delete reads object metadata
(`MetadataClient.list('object')`) and keeps each object whose
`datasource` names the row, the same test the server's delete guard
counts by. While any is listed, the dialog body says the datasource
cannot be deleted yet and asks to delete each object or move it to
another datasource first, and the objects are listed by label with the
name beside it. With none, or when the read fails, the dialog is
unchanged. The delete button stays enabled: the server is still the
judge.
- **The refusal.** `api()` now keeps the HTTP status and the envelope's
`error.code` on the thrown error. A `400` from `DELETE
/api/v1/datasources/:name` re-reads the bound objects; when any remain,
the toast is `engine.datasource.deleteBoundRefused`, naming the
datasource, the objects (joined with the locale's separator row) and the
next step. Every other failure, and a `400` with no bound object, keeps
the server's message in the existing `engine.datasource.deleteFailed`
frame. No server text is matched.
- **i18n (the widening).** Three new `studio` namespace rows, en and zh:
`engine.datasource.deleteBoundBody`,
`engine.datasource.deleteBoundRefused`,
`engine.datasource.boundObjectSeparator` (a punctuation row, since the
package's `lib` is ES2020 and has no `Intl.ListFormat`).
- **Changeset.**
`.changeset/30335-datasource-delete-names-bound-objects.md`,
`@object-ui/app-shell` and `@object-ui/i18n` patch.
Branch: the harness-assigned `claude/project-thread-x1xjpw`, not
`claude/issue-30335-*`; the card is #30335.
## Tests
`DatasourceResourcePage.deleteConfirm-13333.test.tsx` gains a second
top-level describe (5 cases) on the existing harness; expectations are
read from Studio's table, never retyped:
- guard: the two new zh rows are real zh rows;
- the confirm lists the three objects bound to `parts_db` (label with
name, name alone when unlabelled) and not the one on `default`, with the
bound body instead of the plain one;
- a `400` refusal is reported in zh, naming the objects, and the
server's English never reaches the toast;
- CONTROL: no bound object keeps the plain body, and a `400` keeps the
server's message;
- CONTROL: a `503` keeps the server's message even while objects are
bound.
The three existing cases run unchanged (their fake metadata client has
no `list`, which exercises the failed-read path).
## Acceptance notes
- Row Test failure toast (the `test` handler's `${name}: ${message}`) is
untouched: it does not fall out of this change.
- Server half, out of scope here (`packages/**` is the server lane): the
refusal is a plain `Error` answered `400 DATASOURCE_ADMIN_ERROR` with a
count and no object names or `details`, so a client that does not read
object metadata cannot name them. Reported to the seat to file.
- The confirm's list is a metadata read at open time; the server's count
is taken at delete time from the same object listing. They can differ
only if an object is saved in between, and then the server still refuses
and the toast re-reads.
## Verification
Gates and tests ran on `de1864eb`; the head adds an evidence commit and
its removal, the tree is byte-identical (`git diff de1864e HEAD` is
empty). Base `fdd06415`.
<details><summary>Live rig and screenshots (zh-CN, 1440 wide)</summary>
`examples/app-showcase` `objectstack dev --seed-admin` on a private
port, the console via `vite` in `ui/apps/console` with
`DEV_PROXY_TARGET`, Chromium, browser locale zh-CN. A runtime sqlite
datasource `parts_db` (label 零件库) with two objects saved through `PUT
/api/v1/meta/object/:name` naming it (零件 `parts_part`, 供应商
`parts_supplier`).
`DELETE /api/v1/datasources/parts_db` answered `HTTP 400` with
`{"success":false,"error":{"code":"DATASOURCE_ADMIN_ERROR","message":"Cannot
remove datasource 'parts_db': 2 object(s) are still bound to it."}}`,
and `GET /api/v1/meta/object` served both objects with `datasource:
parts_db`.
Before (page restored to the base for the shot): confirm `连接 parts_db
将被移除。此操作不可撤销。`; toast `删除 parts_db 失败:Cannot remove datasource
'parts_db': 2 object(s) are still bound to it.`


After: confirm `以下对象仍在使用 parts_db,暂时无法删除。请先删除这些对象,或把它们改到其他数据源:` with `零件
(parts_part)`, `供应商 (parts_supplier)`; toast `未删除 parts_db:零件
(parts_part)、供应商 (parts_supplier) 仍在使用它。请先删除这些对象或把它们改到其他数据源,再删除
parts_db。`


</details>
<details><summary>Tests, ablation, typecheck, lint</summary>
- `pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/datasource/
packages/i18n/src/__tests__/studio-namespace.test.ts`: Test Files 8
passed (8), Tests 61 passed (61), lock VERDICT command-exit 0.
- Ablation 1 (`scripts/ablation-replace.mjs`, anchor x1 to x0): the
refusal's re-read replaced by an empty list: 1 failed / 7 passed (the zh
refusal case). Restored, blob equals HEAD.
- Ablation 2: `confirmBound` forced empty: 3 failed / 5 passed (the
confirm list case and the two cases that wait on it). Restored, blob
equals HEAD.
- `pnpm --filter @object-ui/i18n --filter @object-ui/app-shell
type-check` (both `tsc --noEmit` and `tsc -p tsconfig.test.json`): lock
VERDICT command-exit 0.
- eslint on the four changed source files: 0 errors, 2 warnings, both on
lines this PR does not change.
- `pnpm check:new-line-citations` (ui): 0 new citations, exit 0.
</details>
<details><summary>Derived gates (35/35 exit 0)</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 35 families at `de1864eb`; every one
ran and exited 0. `--ran` reconciliation: 35 derived, 35 run, 0
NOT-MEASURED (a derived zero, every line carries an exit code). `pnpm
--dir ui exec vitest run scripts/`: 170 files passed, 2 skipped; 4985
tests passed, 2 skipped. CI runs the rest.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgW7fTeU6tAZXwiAJnW)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgW7fTeU6tAZXwiAJnW
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(app-shell): Setup → Datasources names the objects that still use a datasource, in the console's language, before and after its delete
7
+
8
+
Clause-②: yes (widening)
9
+
Metadata-protocol: none
10
+
11
+
-**The confirm.** Opening a datasource's delete reads object metadata and lists every object whose `datasource` names it (label, with the name beside it when they differ). While any is listed, the confirm says the datasource cannot be deleted yet and that each object must be deleted or moved to another datasource first. With none, or when the read fails, the confirm is unchanged.
12
+
-**The refusal.** A `400` from `DELETE /api/v1/datasources/:name` re-reads that list. When objects still name the datasource, the toast says, in the console's language, that it was not deleted, which objects still use it, and what to do next. Any other failure, and a `400` with no bound object, keep the server's message in the existing frame. No server text is matched.
13
+
-**The widening.** Three new `studio` namespace rows in `@object-ui/i18n` (en and zh): `engine.datasource.deleteBoundBody`, `engine.datasource.deleteBoundRefused` and `engine.datasource.boundObjectSeparator`.
Copy file name to clipboardExpand all lines: ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.deleteConfirm-13333.test.tsx
+116Lines changed: 116 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -11,6 +11,11 @@
11
11
* - (b) CONTROL: Cancel sends no `DELETE`.
12
12
* - (c) a row with no label is named by its name.
13
13
* - every case: `window.confirm` is never called.
14
+
*
15
+
* The objects that still use the datasource (the second describe): the confirm
16
+
* lists the objects whose metadata names it, and a `400` refusal is reported in
17
+
* the console's language, naming those objects and what to do. Expectations are
18
+
* read from Studio's table, never retyped; no case matches the server's English.
'engine.datasource.deleteBody': 'The connection {name} is removed. This cannot be undone.',
1982
1982
'engine.datasource.delete': 'Delete',
1983
+
// The objects bound to a datasource, read from object metadata (each object's
1984
+
// `datasource`): the delete confirm lists them, and a refused delete names them.
1985
+
'engine.datasource.deleteBoundBody': 'These objects still use {name}, so it cannot be deleted yet. Delete each object or move it to another datasource first:',
1986
+
'engine.datasource.deleteBoundRefused': '{name} was not deleted: {objects} still use it. Delete these objects or move them to another datasource, then delete {name} again.',
1987
+
'engine.datasource.boundObjectSeparator': ', ',
1983
1988
// objectui#13460 — the datasource page's own copy. Author data (names, driver
1984
1989
// labels and descriptions, server messages) and the driver config schema's enum
1985
1990
// option values (objectui#13353) are not rows here.
0 commit comments