Skip to content

Commit 13d85bb

Browse files
feat(studio): the list-view panel edits a chart view's dataset binding (dataset, measures, dimensions) (#28025)
<!-- claude-projects-attribution: {"github_login":"os-bill"} --> _Requested by **Bill** · [project thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wg4i9JiKjEjwtuVzd4gKy9hB)_ ## 维护者速读 - 改了什么:Studio › 界面 › 列表视图面板,图表视图多了一个「图表数据」区块:按名称列出以本对象为基础的数据集,选定后按名称列出它的度量和维度,每次选择都自动保存到该视图的 `chart`(`dataset` / `values` / `dimensions`),图表类型也可在规范列出的五种里切换。 - 为什么改:图表视图的数据集绑定此前只能在「添加视图」对话框里设一次,建好后在 Studio 里无处可改。 - 风险与回滚:只动 Studio 面板、与「添加视图」对话框共用的一个读取 hook 和新增 i18n 键;换数据集时只保留新数据集也声明的度量/维度,度量清空则像其它必填项一样暂扣不发;整个 PR 回滚即恢复原状,无数据迁移。 - 先看:`ListViewChartSection.tsx` 的 `pickDataset` 与 `MemberList`、`ListViewTypeSection.tsx` 里 `listViewTypeHeldEdit` 新增的 chart 分支、`objectDatasets.ts`(对话框改为经它读取)。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/ObjectListViewInspector.chartBinding-27933.test.tsx`,预期 9 passed。 - 需要你做的:无,席位落地。 Fixes #27933 Clause-②: no Metadata-protocol: none ## What changes - **A "Chart data" section in the list-view panel of a chart view** (`studio-design/ListViewChartSection.tsx`, wired in `ObjectListViewInspector` beside the kanban card and type-settings sections). It edits the list body's `chart` block (`ListChartConfigSchema`): - **Dataset**: the datasets whose base `object` is this object, by label (`LABEL (name)`), never another object's. The draft-overlaid catalog is read inside the Studio's draft world (`useStudioDatasetAuthoring`, as the report and dashboard inspectors do). - **Measures** (`values`) and **Dimensions** (`dimensions`): the bound dataset's own members, picked by label, stored by name, each removable. - **Chart type**: the spec's `chartType` enum, read from the spec's JSONSchema for the block; no option beyond it. - Every pick goes through the same `useObjectListViewDraft` buffer and pillar autosave as every other list-view setting. - **Switching dataset**: the measures and dimensions the new dataset also declares are kept; the rest are dropped, since a name the dataset does not declare binds nothing. A catalog entry that lists no member at all (a name-only summary) keeps none. - **No measure left**: the `values` key is removed rather than written as `[]`. `objectHeldInput` counts only an absent or empty-string input as unset, so `[]` would be sent and refused with a 422; an absent key is held. `listViewTypeHeldEdit` now asks `ListViewSchema` about the `chart` block too, naming the input by the section's own label, so "Show me" focuses it. - **Stale names**: a stored measure or dimension the bound dataset does not declare is shown with a "Not in this dataset" mark and its remove button. A stored dataset that is not one of this object's is shown in the picker, marked. - **Read failures**: the catalog read fails ⇒ the stored dataset, measures and dimensions are shown as saved with the reason, and no picker is offered; the bound dataset's member read fails ⇒ the same for its members. The rest of the panel is unaffected. - **No dataset on the object**: the section says so and draws no picker. - **Shared reading** (`studio-design/objectDatasets.ts`, `useObjectDatasetBinding` + `datasetOptionLabel`): the Create View dialog's own filter-and-resolve code moved here unchanged and the dialog now calls it. The dialog's pickers are single-choice rows in its generic required-pick loop, so the pickers themselves are not shared; the panel's measure and dimension lists are multi-valued. - i18n: new `engine.studio.inspector.listView.chart.*` keys in the studio en and zh packs only; no existing en string changed. ## Tests - New `ObjectListViewInspector.chartBinding-27933.test.tsx`: the real `useObjectListViewDraft` and panel over a real `MetadataClient` whose transport serves the dataset catalog and judges each view PUT with the installed spec's `view` schema. - `CreateViewDialog.chartBinding-11576` and the other dialog suites stay green on the shared hook. ## Acceptance notes - **Shared, not copied — the reading only.** The Create View dialog's filter-and-resolve code is what moved into `useObjectDatasetBinding`; its pickers stay its own (single-choice rows of its generic required-pick loop, where the panel's measures and dimensions are ordered multi-valued lists). Its six suites, `CreateViewDialog.chartBinding-11576` included, and `ObjectView.createChartView-11576` are green on the shared hook. - **Why `values` is removed rather than emptied.** `objectHeldInput` treats only an absent or empty-string input as unset; `values: []` would be judged finished-but-wrong and sent, then refused with a 422 (the spec's `.min(1)`). Removing the key makes the pillar hold the edit with "Measures in the view's Chart settings needs a value", and "Show me" lands on the measure picker (its `label` names the add trigger). - **Name-only catalog entries.** If a dataset list ever answers names without members, switching to such a dataset keeps no measure or dimension (nothing is known to carry over); the members then load through `useDatasetSemantics` as before. - **Draft world.** Inside the Interfaces pillar the section reads `?preview=draft` (the `StudioDatasetAuthoringContext` signal the report and dashboard inspectors use), so a dataset drafted in the package is offered. Outside it, the published list. - **Not changed.** No endpoint, client or reader; no chart rendering; `chartType` offers exactly the spec enum (`bar`, `line`, `pie`, `area`, `scatter`), labelled with the existing widget-type strings. The generic type-settings section still excludes `chart`, and the #27895 control pin for that is unchanged (its docblock now names the new section). `content/docs/frontend/` is not touched, as on the two predecessor panel PRs. ## Verification Head `bab6d6ec`. <details><summary>New suite: 9/9</summary> `pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/ObjectListViewInspector.chartBinding-27933.test.tsx` → Test Files 1 passed (1), Tests 9 passed (9). </details> <details><summary>Neighbouring suites: 35 files, 428 tests</summary> Under `os-verify-lock.sh` (VERDICT command-exit 0): the six `CreateViewDialog*` suites, `ObjectView.createChartView-11576`, all twelve `ObjectListViewInspector*` suites, the three `StudioDesignSurface.listView*` suites, `StudioDesignSurface.kanbanCardSelection-12725`, `datasetCreate`, every `metadataError*` suite, i18n `studio-namespace`, `block-config-i18n` and `product-neutral-renderer.ratchet` → Test Files 35 passed (35), Tests 428 passed (428). </details> <details><summary>Ablations (each restored, blob equal to HEAD, `git diff HEAD` empty)</summary> Through `scripts/ablation-replace.mjs`, anchor 1 → 0 on disk each time; app-shell tests import `src`, so no dist rebuild is involved. 1. `pickDataset` keeps every stored name (the keep filter replaced by `names`) → 2 failed / 7 passed: "another dataset keeps the shared measure, drops what it does not declare…" and "a dataset that shares no measure leaves none…". 2. `listViewTypeHeldEdit`'s chart arm disabled (`type === 'chart' && false`) → 1 failed / 8 passed: "a dataset that shares no measure leaves none: the edit is held…". </details> <details><summary>Type-check and lint</summary> - `pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run type-check` (both `tsc --noEmit` and `tsc -p tsconfig.test.json`) → VERDICT command-exit 0. - `eslint --no-inline-config` over the eight changed source files → 0 errors (warnings are the files' existing ones). - `pnpm --dir ui check:new-line-citations` → 0 new citations; `pnpm --dir ui check:i18n-drift` → 0 en values changed (16 keys added). </details> <details><summary>Derived gates: 34/34 exit 0</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 34 commands on this change set (`check-sdui-manifest` among them); every one ran and exited 0, and `--ran` reconciles: "34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED". Repo-wide lint, Test UI and the type-check lanes are CI's. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01Utp6CXJbywVb2s7U66hkac)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fb3fe52 commit 13d85bb

10 files changed

Lines changed: 927 additions & 41 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): the list-view panel edits a chart view's dataset binding
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
In Studio › Interfaces, the list-view panel of a chart view now has a "Chart data" section. It lists the datasets built on the view's object by label, then the chosen dataset's measures and dimensions by label, and each pick saves to the view's `chart` block (`dataset`, `values`, `dimensions`) as the other settings do. The chart type can be changed there too, among the types the spec lists. Before, a chart view's binding could only be set once, in the Create View dialog.
12+
13+
Picking another dataset keeps only the measures and dimensions that dataset also declares; the others are dropped, since a name the dataset does not declare binds nothing. With no measure left, the edit is held, as an unset required setting is, until one is picked. A stored name the bound dataset does not declare is marked so it can be removed. If the datasets cannot be read, the stored binding is shown as saved with the reason. An object with no dataset says so and offers no picker.
14+
15+
The Create View dialog and the new section read the object's datasets through one shared hook (`useObjectDatasetBinding`); the dialog's behaviour is unchanged.

‎ui/packages/app-shell/src/views/CreateViewDialog.tsx‎

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
* stays open with the user's input when it resolves `false` (objectui#11578).
2222
* The parent persists the view; this dialog WRITES nothing. It does READ:
2323
* while open, for the chart type, it lists the datasets through the metadata
24-
* client (`useDatasetCatalog`, keeping only those whose base `object` is this
25-
* object) and resolves the chosen dataset's measures and dimensions
26-
* (`useDatasetSemantics`), the same pair the dashboard widget editor binds
27-
* with (objectui#11576). A closed dialog, or one with no `objectDef`, fetches
28-
* nothing.
24+
* client and resolves the chosen dataset's measures and dimensions
25+
* (`useObjectDatasetBinding`, objectui#11576: `useDatasetCatalog` keeping only
26+
* the datasets whose base `object` is this object, and `useDatasetSemantics`,
27+
* the pair the dashboard widget editor binds with). The Studio list-view
28+
* panel's chart section reads through the same hook (objectstack#27933). A
29+
* closed dialog, or one with no `objectDef`, fetches nothing.
2930
*/
3031

3132
import { useEffect, useId, useMemo, useRef, useState } from 'react';
@@ -48,11 +49,7 @@ import {
4849
import { useObjectTranslation } from '@object-ui/i18n';
4950
import type { ListViewVisualization } from '@object-ui/core';
5051
import { slugify } from './metadata-admin/createDerive.js';
51-
import {
52-
useDatasetCatalog,
53-
useDatasetSemantics,
54-
type DatasetCatalogEntry,
55-
} from './metadata-admin/previews/useDatasetCatalog.js';
52+
import { datasetOptionLabel, useObjectDatasetBinding } from './studio-design/objectDatasets.js';
5653
import {
5754
deriveFieldOptions,
5855
isImageLikeField,
@@ -247,14 +244,6 @@ interface PickOption {
247244
label: string;
248245
}
249246

250-
/**
251-
* The catalog a dialog that cannot use one asks for: none. Passed to
252-
* `useDatasetCatalog` as its pre-resolved list while the dialog is closed or
253-
* has no object, so mounting it fetches nothing. A module constant, so the
254-
* hook sees one list rather than a fresh array per render.
255-
*/
256-
const NO_DATASETS: DatasetCatalogEntry[] = [];
257-
258247
/** The item a held value none of a picker's options carries is shown by. */
259248
const OUTSIDE_OPTIONS = 'outside';
260249

@@ -325,11 +314,6 @@ function ConfigPicker({
325314
);
326315
}
327316

328-
/** `LABEL (name)`, or the bare name when the dataset declares no other label. */
329-
function datasetOptionLabel(d: DatasetCatalogEntry): string {
330-
return d.label && d.label !== d.name ? `${d.label} (${d.name})` : d.name;
331-
}
332-
333317
const CHART_TYPE_OPTIONS = [
334318
{ value: 'bar', i18nKey: 'console.objectView.chartTypeBar' },
335319
{ value: 'line', i18nKey: 'console.objectView.chartTypeLine' },
@@ -546,20 +530,18 @@ export function CreateViewDialog({
546530

547531
// ── The chart's ADR-0021 dataset binding (objectui#11576) ────────────────
548532
// Called unconditionally (stable hook order). A closed dialog, or one with
549-
// no object to filter by, hands the hook an empty pre-resolved list, so it
550-
// fetches nothing; an open one lists the catalog, and the dialog keeps only
551-
// the datasets whose base `object` is this object. Never the whole list.
533+
// no object to filter by, fetches nothing; an open one lists the catalog,
534+
// keeping only the datasets whose base `object` is this object. Never the
535+
// whole list. Only a dataset this object exposes is ever resolved, so a
536+
// stale or foreign name can never feed the measure and dimension pickers.
552537
const objectName = objectDef?.name;
553-
const catalog = useDatasetCatalog(open && objectName ? undefined : NO_DATASETS);
554-
const objectDatasets = useMemo(
555-
() => (objectName ? catalog.datasets.filter((d) => d.object === objectName) : []),
556-
[catalog.datasets, objectName],
557-
);
558538
const chosenDataset = requiredFieldValues['chart.dataset'] ?? '';
559-
// Only a dataset this object exposes is ever resolved, so a stale or foreign
560-
// name can never feed the measure and dimension pickers.
561-
const boundDataset = objectDatasets.some((d) => d.name === chosenDataset) ? chosenDataset : undefined;
562-
const semantics = useDatasetSemantics(boundDataset, catalog);
539+
const {
540+
catalog,
541+
datasets: objectDatasets,
542+
bound: boundDataset,
543+
semantics,
544+
} = useObjectDatasetBinding(objectName, chosenDataset, { enabled: open });
563545
// What the effects below re-run on: the NAMES offered, never the identity
564546
// of a memoised list (AGENTS.md #10).
565547
const datasetKey = objectDatasets.map((d) => d.name).join('\u0000');

0 commit comments

Comments
 (0)