fix(dashboard): date 型 globalFilter 的预设名默认值应提升为区间 (objectstack#4475) - #3150
Merged
Merged
Conversation
…objectstack#4475)
Setup → System Overview rendered EVERY KPI tile as 0 while its period
selector read "All time" — 200 OK on every request, no error anywhere in
the UI, and zeros that read as "nothing has happened yet" rather than as a
failure.
Both symptoms are one missing normalization. `resolveDashboardFilterDefs`
lifts the built-in `dateRange` declaration's preset NAME to `{ preset }`,
but passed a `globalFilters` entry's `defaultValue` through raw — and
`GlobalFilterSchema.defaultValue` is `string | number | boolean`, so a bare
preset name is the only spelling an author can write. System Overview
declares `{ field: 'created_at', type: 'date', defaultValue: 'last_7_days' }`
and nothing ever mapped it:
- `buildFilterCondition` fell through to its "a bare string date means
equality on that day" branch, so the widget sent
`runtimeFilter: { created_at: 'last_7_days' }` and the backend compiled
`SELECT COUNT(*) AS "user_count" FROM "sys_user" WHERE created_at = $1`
— verified against a live server, byte-for-byte the SQL in the issue.
Actual `sys_user` count is 4; the equality matches no row.
- `DateRangeFilter` derives its selected item from `value.preset` /
`.from` / `.to`, all undefined on a bare string, so the control fell
through to its ALL sentinel and displayed "All time" — which is why the
tiles looked deliberately unfiltered instead of broken.
`normalizeDateDefault` now applies the same lift the sibling `dateRange`
declaration already gets, for `date`/`dateRange` filters whose default names
a preset this module actually knows. A genuine ISO date string still means
equality on that day (the documented behaviour), and numbers, booleans and
unrecognised strings are left exactly as declared.
The backend needs no change: given a real range the dataset path already
lowers it correctly (`WHERE (created_at >= $1 AND created_at < $2)` → 4).
The framework's dashboard metadata needs none either — it is spec-compliant
as written, and editing it would only hide the defect.
Fixes objectstack-ai/objectstack#4475
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
…jectstack#4475) `@object-ui/core` owns the changed source (`dashboard-filters.ts`); the plugin-dashboard side of the change is test-only, so it takes no bump. Levelled `minor` rather than `patch` because the change is visible in rendered dashboards rather than internal: any dashboard declaring a date-typed globalFilters default now emits a different query shape, its numbers change from 0 to real values, and its filter control's displayed label changes with them. Refs objectstack-ai/objectstack#4475 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
Contributor
❌ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 1, 2026
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Contributor
Author
闸门全部完成 ✅正文里" CI(head
|
| 检查 | 结论 |
|---|---|
| Type Check | ✅ success |
| Test (shard 1/4 · 2/4 · 3/4 · 4/4) | ✅ 全部 success |
| Lint | ✅ success |
| Build & E2E | ✅ success |
| Bundle Analysis | ✅ success |
| Changeset Fixed Group Check | ✅ success |
| Dev-server fixture build | ✅ success |
| Build Docs | ✅ success |
| Test (coverage) / dependabot | skipped |
没有失败项,也没有需要归因到 main 既有失败的项。
本地闸门
| 项 | 结果 |
|---|---|
pnpm --filter @object-ui/core test |
17 files / 124 passed |
pnpm --filter @object-ui/plugin-dashboard test |
17 files / 124 passed |
pnpm type-check |
0 个 error TS;CI 上同名 job 独立跑通 |
(顺带一提:本仓库的脚本名是 type-check,不是 framework 那边的 typecheck。)
关于 changeset
按门禁要求补了 .changeset/dashboard-date-filter-preset-default.md:
"@object-ui/core": minor
只有 @object-ui/core 参与版本:改动的源文件 dashboard-filters.ts 属于它;plugin-dashboard 那边只新增了测试文件,不需要 bump。
定 minor 而非 patch 的理由(changeset 正文里有完整论述):这个改动是在渲染出来的 dashboard 上可见的,不是内部细节——任何声明了 date 型 globalFilters 默认值的 dashboard,发出的查询形状变了、数字从 0 变成真值、筛选控件显示的文案也跟着变。任何断言了原先那个条件的东西都会看到它移动。
残留问题
已按 Prime Directive #10 单独立项到 #3151(未认领),正文的"残留问题"小节已更新为该编号。
Generated by Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
修复 objectstack#4475 —— Setup → 系统概览(System Overview)每块 KPI 都读 0。
配套的 framework 侧 PR:objectstack-ai/objectstack#4494(修 #4467 / #4437,同名分支)。framework 侧无需为本缺陷改动任何代码,理由见下。
现象
Setup → 系统概览,周期选择器显示 全部时间 / All time,而每块 KPI 都是 0:
所有请求都是
200 OK,widget 正常渲染,UI 里没有任何失败信号——0 读起来像"还没有数据",而不是像错误。这是最难被发现的那一类。根因:一处缺失的归一化,同时造成两个症状
issue 原文推测是 dataset 路径把 all-time 区间降级成了等值比较。实测证明不是:dataset 路径拿到真正的区间时 lowering 完全正确,它收到的本来就是一个标量。我用 curl 把 Console 实际发出的请求复现了出来(issue 里没能从 curl 复现,关键是
dataset要内联传、selection.measures必须非空):与 issue 里贴的 SQL 一字不差。 而同一条路径拿到区间时:
真正的问题在 Console:一个裸的预设名字符串被当成比较值发了出去。
链路:
system_overview.dashboard.ts声明globalFilters: [{ field:'created_at', type:'date', defaultValue:'last_7_days' }]。这是合规写法——
GlobalFilterSchema.defaultValue是z.union([string, number, boolean]),根本不允许对象形式,所以裸预设名是作者唯一能写的拼法。resolveDashboardFilterDefs对内置dateRange声明会把预设名提升成{ preset }(
defaultValue: preset && preset !== 'custom' ? { preset } : undefined),但
globalFilters分支原样透传f.defaultValue。这个不对称就是本缺陷。buildFilterCondition里typeof v === 'object'为 false,落到"A bare string date means equality on that day" 那一支,原样返回字符串 →
WHERE created_at = 'last_7_days'→ 恒 0。DateRangeFilter的selectValue = value?.preset ?? (value?.from || value?.to ? CUSTOM : ALL),裸字符串上这三个属性全是
undefined,于是落到 ALL 哨兵显示"全部时间",而底下发出去的却是那个字符串。
"显示全部时间"和"KPI 全为 0"不是两件事,是同一个根因的两个症状。 这也是为什么 UI 看起来像"刻意没有过滤、只是恰好没数据"。
改动
normalizeDateDefault给globalFilters分支补上与内置dateRange同一套归一化:type为date/dateRange、且defaultValue是本模块确实认识的预设名时,提升为{ preset }。这不是"在消费端加
??兜底"(AGENTS.md Prime Directive #12):它是同一个归一化函数对兄弟声明补齐同一个转换,而且 spec 只允许标量,producer 侧根本没有别的写法可改。framework 的 dashboard metadata 不应该动——它是合规的,改它只会把缺陷藏起来。保守边界:真正的 ISO 日期字符串仍然表示"当天等值"(既有的文档化行为),数字、布尔、以及不认识的字符串原样保留。
验证
真实 dev server(framework worktree,
pnpm dev -- --fresh -p 38101),对比修复前后 Console 发出的两种runtimeFilter:(该环境 session 实际为 3 条,非 issue 里的 16;要点是 0 → 与
/data一致的真值。)回归测试
packages/core/src/utils/__tests__/dashboard-filters.test.ts新增 4 例:预设名提升为{preset}、提升后产出区间而非等值(并断言 widget-scoped 的runtimeFilter形状)、真正的 ISO 日期仍为等值、非 date 型 filter 不受影响。packages/plugin-dashboard/src/__tests__/DashboardFilterBar.dateDefault.test.tsx:直接用 System Overview 的声明原文断言控件显示"Last 7 days"而非"All time";并保留"值确实为空时仍显示 All time"。pnpm --filter @object-ui/core testpnpm --filter @object-ui/plugin-dashboard testpnpm type-check残留问题(已单独立项:#3151)
date型 filter 拿到一个既不是已知预设、也不是合法 ISO 日期的字符串时,仍会静默降级成一个永不命中的等值比较,继续产生"看着健康的 0"。预设名这一类由本 PR 覆盖,拼错的自定义值不会。按 Prime Directive #10 单独记录在 #3151(未认领),不在本 PR 扩大范围。Fixes objectstack-ai/objectstack#4475