Found while fixing objectstack#4475 (Setup 系统概览 KPI 全为 0)。#4475 的修复覆盖了预设名这一类;这一条是它旁边没被覆盖的姐妹情形,单独立项以免扩大那个 PR 的范围。
现象
date / dateRange 型的 dashboard filter 拿到一个既不是已知预设名、也不是合法 ISO 日期的字符串值时,会被静默降级成一个永远命中不了任何行的等值比较。请求返回 200 OK,widget 正常渲染,数字是 0——和"这个区间确实没有数据"完全无法区分。
复现
任意 dashboard 声明一个拼错的 date filter 默认值:
globalFilters: [
{ field: 'created_at', type: 'date', label: 'Date Range', defaultValue: 'last_7_dayz' },
// ^ 拼错
]
@object-ui/core 的 buildFilterCondition 走到这一支:
if (def.type === 'dateRange' || def.type === 'date') {
const v = value as DateRangeValue;
if (typeof v === 'object') { /* 预设 / from-to → 区间 */ }
// A bare string date means equality on that day.
return value; // ← 'last_7_dayz' 原样成为比较值
}
于是 widget 发出 runtimeFilter: { created_at: 'last_7_dayz' },后端如实编译:
SELECT COUNT(*) AS "user_count" FROM "sys_user" WHERE created_at = $1
零行。同一条路径上,'last_7_days'(拼对)在 objectstack#4475 修好后会被提升成
{ $gte: '{7_days_ago}', $lte: '{today}' },得到正确的计数。
{ preset: 'last_7_dayz' } 这种对象形式也一样:PRESET_RANGES[v.preset] 查不到 → from/to 都是 undefined → buildFilterCondition 返回 undefined → 该 filter 被整个丢弃。这一支至少是放宽(不过滤)而不是归零,但同样是静默的。
为什么值得修
和 objectstack#4475 是同一类失败模式,而且是其中更隐蔽的方向:0 看起来像一个合法答案。没有 4xx、没有 console 警告、没有任何 UI 信号,作者拿到的是"这个筛选条件下没有数据"这个完全合理的结论。objectstack#4475 花了一整轮 RC 验证才被抓到,靠的是有人去读响应里的 sql 字段。
对比一下同一模块里已有的严格度:buildWidgetScopedFilter 在 knownFields 可用时,对默认绑定到不存在字段的 filter 会跳过并打 console.warn,理由正是"不要发一个后端只会空匹配或直接拒绝的查询"。字段名走了这条路,字段值没有。
期望
date/dateRange filter 的字符串值应当在三条里择一,而不是无声落到等值:
- 是已知预设名 → 提升为区间(objectstack#4475 已实现);
- 是可解析的 ISO 日期 → 当天等值(现有的文档化行为,保留);
- 两者都不是 → 拒绝或跳过,并打 console.warn 指名该 filter 与那个值,与
buildWidgetScopedFilter 对未知字段名的处理保持同一种严格度。
第 3 条是当前缺失的那条。至于是"跳过 + 警告"(与未知字段名一致,倾向放宽)还是在作者时就拒绝,需要定夺——GlobalFilterSchema.defaultValue 是 string | number | boolean,所以校验能不能上移到 spec/lint 层也是同一个决策的一部分。
关联
Found while fixing objectstack#4475 (Setup 系统概览 KPI 全为 0)。#4475 的修复覆盖了预设名这一类;这一条是它旁边没被覆盖的姐妹情形,单独立项以免扩大那个 PR 的范围。
现象
date/dateRange型的 dashboard filter 拿到一个既不是已知预设名、也不是合法 ISO 日期的字符串值时,会被静默降级成一个永远命中不了任何行的等值比较。请求返回200 OK,widget 正常渲染,数字是 0——和"这个区间确实没有数据"完全无法区分。复现
任意 dashboard 声明一个拼错的 date filter 默认值:
@object-ui/core的buildFilterCondition走到这一支:于是 widget 发出
runtimeFilter: { created_at: 'last_7_dayz' },后端如实编译:零行。同一条路径上,
'last_7_days'(拼对)在 objectstack#4475 修好后会被提升成{ $gte: '{7_days_ago}', $lte: '{today}' },得到正确的计数。{ preset: 'last_7_dayz' }这种对象形式也一样:PRESET_RANGES[v.preset]查不到 →from/to都是 undefined →buildFilterCondition返回undefined→ 该 filter 被整个丢弃。这一支至少是放宽(不过滤)而不是归零,但同样是静默的。为什么值得修
和 objectstack#4475 是同一类失败模式,而且是其中更隐蔽的方向:0 看起来像一个合法答案。没有 4xx、没有 console 警告、没有任何 UI 信号,作者拿到的是"这个筛选条件下没有数据"这个完全合理的结论。objectstack#4475 花了一整轮 RC 验证才被抓到,靠的是有人去读响应里的
sql字段。对比一下同一模块里已有的严格度:
buildWidgetScopedFilter在knownFields可用时,对默认绑定到不存在字段的 filter 会跳过并打 console.warn,理由正是"不要发一个后端只会空匹配或直接拒绝的查询"。字段名走了这条路,字段值没有。期望
date/dateRangefilter 的字符串值应当在三条里择一,而不是无声落到等值:buildWidgetScopedFilter对未知字段名的处理保持同一种严格度。第 3 条是当前缺失的那条。至于是"跳过 + 警告"(与未知字段名一致,倾向放宽)还是在作者时就拒绝,需要定夺——
GlobalFilterSchema.defaultValue是string | number | boolean,所以校验能不能上移到 spec/lint 层也是同一个决策的一部分。关联