Repository navigation
Commit 6f095b9
feat(studio): customize a read-only package's dataset in the All metadata area, see what uses it before publishing, and read a refused publish at the dataset (#30224)
domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3
Fixes #30064
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- **改了什么**:Studio「全部元数据」里,只读基础包(如
CRM)的数据集不再只能看:打开后是数据集设计器加实时预览,标题栏有「定制」。第一次保存把整个数据集复制进配对包 `BASE.custom`
并声明 `overrides: { package, basedOn
}`,顶栏「发布」生效,覆盖标记里的「恢复软件包原版」撤销。编辑器上方列出用到这个数据集的仪表盘、报表等(来自 引用关系
读取),发布被拒时原因按控制台语言显示在数据集编辑器里,并定位到具体度量和输入项。
-
**为什么改**:客户对「在途商机总额」的口径和厂商不同,只能改仪表盘、改不了下面的数字;而一个数据集同时喂给所有基于它的仪表盘和报表,发布前必须让人看到会影响哪些。
- **风险与回滚**:只改
ui(`@object-ui/app-shell`、`@object-ui/data-objectstack`、`@object-ui/i18n`),没有服务端或
spec 改动。可写包里的数据集仍走原来的通用条目页。顺带修了一个已发布缺陷:`MetadataClient.references()`
读的是服务端从不返回的 `items` 键,Setup 的「引用关系」页和 Studio
条目工具对每个条目都显示「未找到引用,可以安全删除」;现在按声明的 `{ references: [...] }`
解析,读不懂的答复算读取失败。回滚 = revert 本 PR。
- **先看**:① `ui/packages/data-objectstack/src/metadata-client.ts` 的
`references()`(引用读取的修复,影响 Setup 引用页);②
`ui/packages/app-shell/src/views/studio-design/StudioBundleItemEditor.tsx`
的 `StudioBundleUsedBy` 与设计器挂载(`Designer` 分支、`blocking` 拦截自动保存);③
`metadataError.ts` 的 `datasetMemberLocator`。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/StudioBundleItemEditor.customize-28569.test.tsx
packages/data-objectstack/src/metadata-client.references-30064.test.ts`,预期
16 例全绿(其中 10 例是本卡新增)。
- **需要你做的**:无,席位落地。
## What changes
1. **The door.** `metadataArea.ts` gains `AREA_CUSTOMIZE_EDITOR_TYPES`
(`dataset`): on a READ-ONLY package the All metadata area opens a
dataset in its form editor (`StudioBundleItemEditor`), the same editor
that customizes an email template or a translation.
`areaItemOpensFormEditor` takes the area's `readOnly` gate; on a
writable package a dataset still opens the generic item page, as on
Setup's. `dataset` is `overridable` in `OVERRIDE_TYPE_CLASSIFICATION` (`
dataset: listed` re-checked on the base).
2. **The designer in that editor.** A type with a registered default
inspector is edited in it rather than in the generic form: the dataset
gets `DatasetDefaultInspector`, beside its registered `DatasetPreview`.
Its shallow patch is applied to the buffer, and a blocking issue it
reports (a dimension with no field) holds the autosave, as the generic
page does. Email templates and translations register none, so they are
unchanged.
3. **Customize, saved whole into the pair, with Restore.** Nothing new:
the editor's existing route (#28711's controls, #28569's editor). The
first saved edit asks the ensure door once, then writes the whole
dataset into `BASE.custom` declaring `overrides: { package, basedOn }`
(`overrideDraftBody`); the header's Publish publishes the pair; the
override mark's popover offers "Restore package original". A note before
the first save says the whole dataset is copied.
4. **What uses it, before publish.** While the dataset is customized,
the editor lists every item that names it, one entry per item, each
opening in the area, from the existing references read
(`client.references`, the item tools' 引用关系). A failed read says it
failed and offers Retry; it never reads as "nothing uses it". No new
server read.
5. **The references read reads its declared answer.** `GET
/meta/:type/:name/references` serves `{ references: [{ type, name,
label?, path, kind }] }` bare (`FindReferencesToMetaResponseSchema`);
`MetadataClient.references()` read `data.items`, which the route never
serves, so it returned `[]` for every item and both panels said "Safe to
delete". It now parses the declared answer and maps each element to
`MetadataReference` (`fromType`, `fromName`, new optional `fromLabel`
and `kind`); an answer the declaration refuses throws. Measured on the
CRM example before the fix: the route answered the Pipeline Dashboard
for `opportunity_metrics`, and the reader dropped it.
6. **A refused publish, at the item.** `publishLocator` gains
`datasetMemberLocator`: an issue under `measures.N` or `dimensions.N` is
placed on that member by its label and on the designer's input (`Not
published. Check Aggregate of the measure "Total Amount": Details say
what to change.` / 「未发布。请检查度量「总金额」的「聚合方式」」). The surface hands the area
its kept refusals (`publishRefusalFor`), and the editor shows the one
for its item, as the Pending changes entry does. The server's text stays
under Details, verbatim (#26730's rule).
7. **Marks, the 仅看定制 count and the tile.** No code: they read the
customization read by type, and it lists the dataset override. Measured
in the browser (below).
Why `field: expected_revenue` was refused: `expected_revenue` is a
`formula` field, and the authoring gate's
`measure-aggregate-field-type-refused` refuses `sum`/`avg` over a
formula field (virtual, no stored column; its hint: store the value as a
number field and aggregate that). Acceptance 6: no server or spec change
was needed.
## Tests
- `StudioBundleItemEditor.customize-28569.test.tsx` (the editor's shared
harness), new describe "Customize a read-only base's dataset
(objectstack#30064)", 6 cases: designer read-only before Customize, the
whole-dataset note, the used-by list deduplicated per item; Customize +
an edit write the whole dataset into the pair, declared, base untouched;
a blocking report holds the edit unsent; a failed references read says
so and Retry reads again; an empty answer says nothing uses it; a kept
publish refusal shows at the item with Details.
- `metadata-client.references-30064.test.ts` (new, 4 cases): the fixture
is the declared shape; mapping with and without a label; the honest
empty answer; an off-contract answer throws.
- `metadataError.publishRefusal-26730.test.ts`, new describe (2 cases):
the measured formula refusal placed on the measure's Aggregate in zh,
engine text kept out of the sentence; a dimension named by its name; a
path under no member left unplaced.
- `metadataArea.test.ts`, new describe (3 cases): dataset opens the
editor only on a read-only package; the editor's exceptions kept; email
template and translation unchanged.
<details><summary>Test runs (head aefff1f unless noted)</summary>
- `pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/` → `Test Files 324 passed
(324)` · `Tests 2412 passed (2412)`; lock `VERDICT command-exit 0`.
- `pnpm --dir ui exec vitest run packages/data-objectstack/
packages/i18n/ packages/app-shell/src/views/item-tools/
packages/app-shell/src/views/metadata-admin/ResourceEditPage.references
packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector`
on 66709ce → 1 red: `studio-namespace.test.ts` placeholder parity
(`usedByOne` en had no `{count}`); fixed in aefff1f, then `pnpm --dir
ui exec vitest run packages/i18n/` → `Test Files 95 passed (95)` ·
`Tests 1443 passed | 13 skipped`.
- `pnpm --filter @object-ui/data-objectstack --filter @object-ui/i18n
--filter @object-ui/app-shell run type-check` → all three `Done`, lock
`VERDICT command-exit 0` (after building the app-shell dependency
closure with declarations).
- Ablation 1 (`node scripts/ablation-replace.mjs`, committed head):
`blocked: !editable || restoring || blocking > 0,` → `blocked: !editable
|| restoring,` — anchor 1 → 0, blob 8500a76 → 53bc7c49; "holds an edit
the designer reports as blocking" `×`, `Tests 1 failed`; restored blob
== HEAD, `git diff HEAD` empty.
- Ablation 2: `fromType: ref.type,` → `fromType: ref.kind,` — anchor 1 →
0; "maps each served reference …" `×`, `Tests 1 failed | 3 passed`;
restored blob == HEAD.
- Both ablated files import their subject from source
(`./metadata-client`, `./StudioBundleItemEditor`), not a built `dist/`.
</details>
## Acceptance notes
- **Published surface (Clause-② yes, widening).** `MetadataReference`,
exported from the `@object-ui/data-objectstack` entry, gains two
optional fields: `MetadataReference.fromLabel` (the referring item's
label, when served) and `MetadataReference.kind` (the served reference
kind). The same diff narrows what `MetadataClient.references()` accepts:
a 200 answer `FindReferencesToMetaResponseSchema` refuses now throws
instead of resolving `[]` (a non-2xx answer already threw). Every caller
in this repository reads it through `useReferencesScan`
(`ui/packages/app-shell/src/views/item-tools/ReferencesPanel.tsx`),
whose `catch` sets the `error` arm, and none calls it bare: Setup's item
page (`ResourceEditPage.tsx`, the References sheet) and Studio's item
tools (`StudioItemTools.tsx`, the References sheet) both render
`ReferencesPanel`'s error arm on the throw, the `refsErrorTitle` /
`refsErrorDesc` sentences (the check did not finish, nothing said about
deleting), the cause, and Retry, with no count badge; the new used-by
list (`StudioBundleItemEditor.tsx`, `StudioBundleUsedBy`) shows
`usedByError` with Retry. Callers in other repositories were not read.
- **In-place fix, named:** the references read (item 5 above). It is the
read this card was told to reuse; the card's Acceptance 2 cannot be met
with it returning `[]`. Evidence: the route's answer for
`opportunity_metrics` on the CRM example was
`{"references":[{"type":"dashboard","name":"pipeline_dashboard","label":"Pipeline
Dashboard","path":"widgets[].dataset","kind":"dashboard dataset"}]}`,
and the reader took `data?.items ?? []`. The same fix changes what
Setup's References tab and Studio's item tools show for every type.
- The dataset designer keeps its own count line ("Bound by 0 reports · 1
dashboard"), read by a client-side list scan (`useDatasetUsage`); the
new list is the references read. Two reads of one fact sit on the page;
they agreed in the browser check. Not changed here.
- A refused publish's reason (here: a formula field stores no value)
stays in the server's English under Details; the Console sentence names
the measure and its input. A designer sentence per rule would be a
separate decision.
- Seed data differs from the card's: the CRM example's 在途商机总额 reads
710,000 here, not ¥814,400.
## Verification
Browser check on the CRM example (`objectstack dev --fresh`, own port)
with the Console's Vite dev server on its own port, Chromium, locale
zh-CN. Screenshots are in the dev's scratchpad, not uploaded.
<details><summary>Steps and readings</summary>
1. `/studio/com.example.crm/metadata/dataset/opportunity_metrics`:
designer read-only, 定制 in the header, the whole-dataset note,
「发布后,这里的修改会影响基于此数据集的 1 项:Pipeline Dashboard 仪表板」.
2. 定制 → measure 2 (`total_amount`) 聚合方式 求和 → 平均值: status 未发布草稿, mark 覆盖
CRM (minimal example); the pair's draft read back with `overrides:
{"package":"com.example.crm","basedOn":"4.0.0"}` and all three measures.
3. 发布 → 全部发布: `GET /packages/com.example.crm/customizations` lists
`{"type":"dataset","name":"opportunity_metrics","basedOn":"4.0.0"}`.
Dashboard 在途商机总额 710,000 → ¥177,500.
4. Directory tile 数据集: 已定制 1; dataset list: 仅看定制(1), row mark 已定制(CRM
(minimal example)).
5. Measure 2 字段 金额 → 预期收入 (`expected_revenue`, formula), 发布: Pending
changes entry and the dataset editor both read
「未发布。请检查度量「总金额」的「聚合方式」:详情中说明了需要修改的内容。」, Details holds the gate's line.
6. 覆盖标记 → 恢复软件包原版 → confirm: mark gone, 定制 back, customizations read
empty; dashboard 在途商机总额 ¥710,000.
</details>
<details><summary>Gates (local, head aefff1f)</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 35 commands; every one exited 0
(`check-adr-0087-registration`, `check-changeset-no-major`,
`check-closing-keyword-parity`, `check-comment-mask-corpus`,
`check-empty-changeset`, `check-sdui-manifest` and `--self-test`,
`check:nul-bytes`, `check:published-files`, `check:type-check-coverage`,
`check:type-check-debt`, `check:undeclared-dep-imports`,
`check:test-source-alias`, `check:cross-package-test-inputs`, `pnpm
--dir ui exec vitest run scripts/` (`Test Files 170 passed | 2 skipped`)
and the rest). `--ran` reconciliation: `35 derived famil(ies) accounted
for — 35 run, 0 NOT-MEASURED (a DERIVED zero — all 35 recorded an exit
code and none of them is 3)`.
`ui/scripts/check-new-cross-file-line-citations.mjs`: `0 new
citation(s)`. The type-check lanes and the wide-population families are
CI's.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QSeKYnE9YNkSZen7sijLA3)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 490d136 commit 6f095b9
14 files changed
Lines changed: 682 additions & 28 deletions
File tree
- .changeset
- ui/packages
- app-shell/src/views/studio-design
- data-objectstack/src
- i18n/src/namespaces/studio
| 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 | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
27 | 33 | | |
28 | 34 | | |
29 | 35 | | |
| |||
90 | 96 | | |
91 | 97 | | |
92 | 98 | | |
| 99 | + | |
93 | 100 | | |
94 | 101 | | |
95 | 102 | | |
| |||
115 | 122 | | |
116 | 123 | | |
117 | 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 | + | |
118 | 162 | | |
119 | 163 | | |
120 | 164 | | |
| |||
134 | 178 | | |
135 | 179 | | |
136 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
137 | 212 | | |
138 | 213 | | |
139 | 214 | | |
| |||
146 | 221 | | |
147 | 222 | | |
148 | 223 | | |
| 224 | + | |
149 | 225 | | |
150 | 226 | | |
151 | 227 | | |
| |||
155 | 231 | | |
156 | 232 | | |
157 | 233 | | |
| 234 | + | |
158 | 235 | | |
159 | 236 | | |
160 | 237 | | |
| |||
196 | 273 | | |
197 | 274 | | |
198 | 275 | | |
| 276 | + | |
199 | 277 | | |
200 | 278 | | |
201 | 279 | | |
202 | 280 | | |
203 | 281 | | |
204 | 282 | | |
205 | 283 | | |
| 284 | + | |
206 | 285 | | |
207 | 286 | | |
208 | 287 | | |
| |||
228 | 307 | | |
229 | 308 | | |
230 | 309 | | |
| 310 | + | |
231 | 311 | | |
232 | 312 | | |
233 | 313 | | |
| |||
437 | 517 | | |
438 | 518 | | |
439 | 519 | | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
0 commit comments