Skip to content

fix(plugin-report): report chart 的度量显示名按三级回落解析,不再印原始 name (#4020) - #4879

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-4020-report-axis-measure-label
Aug 16, 2026
Merged

fix(plugin-report): report chart 的度量显示名按三级回落解析,不再印原始 name (#4020)#4879
yinlianghui merged 2 commits into
mainfrom
claude/issue-4020-report-axis-measure-label

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4020

病灶

数据集绑定的 report chart 把度量原样交给图表组件,实测转发出去的 schema 键只有六个:

chartType, data, height, isAnimationActive, series, xAxisKey

其中 series[{ dataKey: yAxis }] —— 没有 label。ChartRenderer 随后用 s.label || s.dataKeyconfig[dataKey].label,于是图例、标记 tooltip 的 name、以及单值族卡片的说明文字同时落回 dataKey,在全中文控制台上打出 potential_upside_tons。同一张报表下方的汇总表读 headerLabel()(结果字段 label)一直是对的,绑定同一数据集的 dashboard 图表走 buildChartSeriesfields[].label)也一直是对的 —— 报表图是唯一两边都不查的那一面,立卡人的定位准确。

ReportChartSchema.series[].label 自 rc.1 起就已声明,但这条路径从未读过它。立卡人把它实测为惰性元数据,属实:作者因此没有任何可授权的手段控制这个字符串。

修法

DatasetReportChart 里把度量显示名解析一次,系列图分支与单值族分支共用(原先两条分支各写各的,正是漂移的温床):

  1. chart.series[]name 命中该度量的条目的 label —— spec 的 I18nLabel,经 pickLocalized 按控制台语言解析;同名重复以第一条为准,与 dashboard 的 mergeAuthoredPresentation 同一裁定;
  2. 绑定数据集的度量 label —— 即结果字段的 labelheaderLabel() 读的正是这一个值,所以图和它下方的汇总表在一屏上只有一种拼写;
  3. 度量 name 兜底,headerLabel() 自己的最后一档。

复用而非新造口径:② 和 ③ 直接落在既有共享 helper buildDatasetFieldHelpers().headerLabel@object-ui/core,汇总表、矩阵表头、dashboard 表格、DatasetPreview 都在用);① 的 i18n 记录形用 @object-ui/i18npickLocalized —— 全仓解析 I18nLabel 的那一个 helper。没有引入第三份拼写。

语言取 useObjectTranslation().language 而非本组件已有的 useDisplayLocale():后者优先租户区域 locale(ADR-0053)因为它喂 Intl 数字格式;数字按 de-DE 格式化的工作区若控制台跑 zh,标签仍应读 zh 那一支。

tooltip / legend 同源同修:三者读的是同一个 config[dataKey].label,所以一处 label 落位,图例、tooltip、单值说明文字一起对 —— 不是第二次解析,是同一个输入。

反向验证:两种探针,方向分别预判后再跑,都对上了

"把修改撤掉看钉子红"在这张卡上是歧义的 —— 有两种撤法,动的不是同一批用例。两种都先写预判再跑:

探针 B(measureLabel = yAxis,name 直读):预判 9 红 1 绿,绿的必须是 ③。实测一致:

× ① an authored chart.series[].label naming the measure wins
× ② with no series authored, the dataset measure label is the default
× the chart series and the summary-table header read the SAME label
AssertionError: expected 'potential_upside_tons' to be '本季可抢吨位'
AssertionError: expected 'potential_upside_tons' to be '可抢吨位(吨)'
 Tests  9 failed | 1 passed (10)

③ 活下来是必然而非侥幸:name 直读与三级回落的第 ③ 档重合,这条用例在原理上就分辨不了两者。每条失败打印的都是 potential_upside_tons —— 立卡正文报的症状原样复现。

探针 A(还原改动前的真实两行):预判同样 9 红 1 绿,但绿的是另一条。实测一致:

× ③ with neither, the measure name is the last resort
AssertionError: expected undefined to be 'potential_upside_tons'
× a kpi caption honours the authored series label over the dataset one
AssertionError: expected '120可抢吨位(吨)' to contain '本季可抢吨位'
 Tests  9 failed | 1 passed (10)

③ 在这里转红:改动前转发的 series 根本没有 label 键,值是 undefined 而不是 name。唯一的绿是 "a kpi caption still falls back to the dataset measure label" —— 它记录了一个真实事实:单值族分支改动前就已解析 ② 和 ③,缺的只有 ①;系列图分支三档一档都没有。

所以 ③ 的职责不是"改前红",而是守住兜底仍落在 name 上、没变成 undefined[object Object] —— 这正是改动前代码真实具备的缺陷,而 ① ② 两条看不见它。两种探针的方向差异已如实写进用例文件头,不按"改前绿改后红"的模板硬套。

测试

新增 DatasetReportRenderer.chartMeasureLabel.test.tsx(10 条):三级各一、命名了别的度量的条目不得越权改名、命中但无可用 label 落回 ②、同名重复第一条胜、i18n 记录形按控制台语言取 zh 支、table 与 chart 同度量同名(本卡病灶的反面)、单值族 ① 与 ②。

pnpm exec vitest run packages/plugin-report --maxWorkers=2
 Test Files  10 passed (10)
      Tests  111 passed (111)

pnpm exec vitest run .../ReportPreview.dataset.test.tsx --maxWorkers=2   # 消费半径
 Test Files  1 passed (1)   Tests  7 passed (7)

pnpm exec turbo run type-check --concurrency=2
 Tasks:    81 successful, 81 total

pnpm --filter '@object-ui/plugin-report' lint   →  0 errors(74 条既有 warning)
node scripts/check-control-bytes.mjs            →  OK (4363 tracked text files)

消费半径按规矩点过:DatasetReportRenderer 的调用方是 app-shell 的 ReportView 与 metadata-admin 的 ReportPreview,两者的 dataset 用例均绿;没有任何别处 fixture 断言这条转发出去的 series 形状。

顺手发现,不扩围(已另立单)

两条都是实测到接缝、非代码阅读推断;均未认领,留给 PM 分诊。


Generated by Claude Code

yinlianghui and others added 2 commits August 16, 2026 21:18
数据集绑定的 report chart 把度量原样交给图表组件(`series: [{ dataKey }]`,
无 label),于是 `ChartRenderer` 用 dataKey 填 `config[dataKey].label`,图例、
标记 tooltip 与单值卡片说明文字一起在全中文控制台上打出
`potential_upside_tons`;同一张报表下方的汇总表、以及绑定同一数据集的
dashboard 图表都能正确解析授权 `label`。`ReportChartSchema` 自 rc.1 起声明的
`series[].label` 也从未被读取——立卡人实测其为惰性元数据,属实。

按三级回落解析一次,系列图与单值族共用:

1. `chart.series[]` 中 `name` 命中该度量的条目的 `label`(spec 的 I18nLabel,
   经 `pickLocalized` 按控制台语言解析;同名重复第一条为准,与 dashboard 的
   `mergeAuthoredPresentation` 同裁定);
2. 绑定数据集的度量 label(结果字段 `label`)——`headerLabel` 读的正是这一个
   值,所以图与其下方汇总表在一屏上只有一种拼写;
3. 度量 `name` 兜底(`headerLabel` 自己的最后一档)。

语言取 `useObjectTranslation().language` 而非 `useDisplayLocale()`:后者优先
租户区域 locale(ADR-0053)以喂 `Intl` 数字格式,而数字按 de-DE 格式化的工作区
若控制台跑 zh,标签仍应读 zh 那一支。

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-CVeA6RJq.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.36KB 111.03KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 157.05KB 43.28KB
fields (index.js) 231.73KB 57.60KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.86KB 10.83KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.75KB 18.37KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 128.14KB 32.58KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.81KB 59.97KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 115.34KB 27.74KB
plugin-gantt (index.js) 164.06KB 39.94KB
plugin-grid (index.js) 192.23KB 51.30KB
plugin-kanban (index.js) 52.72KB 14.54KB
plugin-list (index.js) 111.23KB 26.97KB
plugin-map (index.js) 17.91KB 5.72KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.70KB 11.19KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.53KB 9.41KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

【PM 验收 · ACCEPT】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt)对 #4020 的实施验收:

实物核验:3 文件(DatasetReportRenderer +67/-4、新钉 10 条、changeset patch),零越包、模型标识 0、releases 0。前置硬规兑现:objectstack#5141 源单全线程读毕(分诊裁定与本卡一致、迁移属「移走非驳回」、无 hold 条件)。口径纪律:②③ 复用 buildDatasetFieldHelpers().headerLabel(汇总表/矩阵/dashboard 表格同款,未造第三份口径);① 的 I18nLabel 用全仓唯一的 pickLocalized;measureLabel 解析一次、系列图与单值族两分支共用 —— 消灭各写各的漂移温床。语言取 useObjectTranslation().language 而非租户 locale 的理由(ADR-0053 区分数字格式与文本支)成立。

反向验证(双探针):A(还原真实旧两行)与 B(name 直读)各 9 红 1 绿且绿点不同,预判全对上;探针 A 还记录了「单值族原已有 ②③、系列图三档全无」的真实事实 —— 撤法歧义被如实拆解而非硬套模板。CI 亲读:19 项全 completed(17 success + 2 skipped),零失败。

留否决窗一处:① 对 I18nLabel 按控制台语言解析,与 plugin-charts/dashboard 两处 locale-blind 的「第一个字符串胜」存在有意取向差异 —— 本支更正确(zh 控制台不吞英文支)且不同屏不打架,已在代码注释与 PR 声明;若维护者倾向全仓一致,反向收敛(charts 侧改 pickLocalized)另开一单即可。新 findings #4877(转发丢 chrome 且 showLegend: false 被反转)、#4878(report 路径缺 NULL 分桶整族保护)记账合格,留分诊。

处置:undraft + auto-merge(SQUASH)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 16, 2026 22:25
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 16, 2026
Merged via the queue into main with commit 6bb39c4 Aug 16, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4020-report-axis-measure-label branch August 16, 2026 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant