fix(dashboards): drop the Service dashboard date range — datetime filters match nothing (#460) - #546
Merged
Conversation
The Customer Service dashboard opened on all zeros — every KPI 0, every chart reporting no rows — with 38 cases in the system. `dateRange.defaultRange` was `last_30_days` while the demo cases run from 1 to 30 days old, so the default window sat exactly on the edge of the data it aggregates. The runtime ANDs the dashboard range into every widget query, so the oldest cases dropped out and a business user's first paint looked like a broken screen. Default to a rolling `last_90_days`, which always contains the full case history whatever day the demo is opened on. `this_quarter` — what the CRM, Sales and Executive dashboards use — is deliberately not the fix: those window `close_date` over a forward-looking pipeline, where a calendar quarter is the intended framing, whereas a support desk reads a trailing window, and a calendar quarter is only a few days long on 1 July, which would reintroduce the same emptiness. Add a regression guard that resolves the default preset against every reference day of a leap year and requires the window to clear the oldest seeded case by a margin, so a flush-to-the-edge default fails in CI instead of in a demo. Verified it fails on `last_30_days` and passes on `last_90_days`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZJSA2ZpuwDtz2Nc8bgXjy
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
…ters match nothing (#460) Running the app disproved the diagnosis in the previous commit. Widening the preset does not fix #460; nothing about the window does. `crm_case.created_date` is a `Field.datetime()`. On the SQLite path `driver-sql` 16.1.0 coerces datetime filter values to epoch-millisecond INTEGERs (`coerceFilterValue`), on the documented assumption that datetime columns hold INTEGER ms. They hold ISO TEXT — including the platform's own `created_at`/`updated_at` audit columns. SQLite orders every INTEGER before every TEXT, so on a datetime column: created_date >= <int> is TRUE for every row created_date <= <int> is FALSE for every row The runtime ANDs the dashboard range into every widget query, so the `$lte` half zeroed the whole dashboard at any preset. Measured against the running 16.1.0 console: `$gte` alone returns all 38 cases, `$lte` alone returns 0, both bounds return 0, in every date format tried. Remove the `dateRange` block instead of widening it. The dashboard now renders 30 open / 7 critical / 45.0h / 3 SLA breaches with every chart populated, confirmed in the browser. The cost is visible: no date picker on this dashboard until the driver is fixed. The block is left commented-out at the spot to restore. Also document that `daily_case_volume`'s `$gte: '{30_days_ago}'` floor is inert for the same reason, so that chart currently plots every case. Replace the previous window-arithmetic test, which could not detect this class of defect, with a guard that fails if any dashboard windows a `datetime` field, plus one checking the range field exists on the objects its widgets aggregate. Verified failing when the removed block is restored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZJSA2ZpuwDtz2Nc8bgXjy
yinlianghui
marked this pull request as ready for review
July 30, 2026 02:06
…fect (#460) A duplicate check on the platform repo found this already reported: objectstack-ai/objectstack#3912, closed 2026-07-29, same mechanism and same measurements, filed while upgrading this app 16.1.0 → 17.0.0-rc.0. No new upstream issue needed. More importantly it turned up #3777 (open, p1), which is a SEPARATE datetime bug: a bare `YYYY-MM-DD` `$lte` upper bound on a datetime column drops every record created after 00:00 that day, silently, by an amount that grows through the day. So a platform upgrade alone does not make it safe to restore this dashboard's date range — both fixes must land first. Record both in the dashboard comment and the changeset so the restore condition is precise rather than "once it's fixed upstream". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZJSA2ZpuwDtz2Nc8bgXjy
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZJSA2ZpuwDtz2Nc8bgXjy
This was referenced Jul 30, 2026
This was referenced Aug 14, 2026
16 tasks
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.
Description
service_dashboardopened with every KPI at0and every chart reporting no rows, with 38 cases in the system. The preset was never the cause, and no preset fixes it.crm_case.created_dateis aField.datetime(). On the SQLite path,driver-sql16.1.0 coerces datetime filter comparands to epoch-millisecond INTEGERs while writes store ISO TEXT. SQLite orders every INTEGER before every TEXT, so on a datetime column:The runtime ANDs the dashboard range into every widget query, so the
$ltehalf zeroed the whole dashboard.Measured, not inferred
Against the running 16.1.0 console (
pnpm demo:reset && pnpm dev, logged in, samecase_metricsdataset the widgets use, varying only thecreated_datefilter):$gteonly (2026-05-01)$lteonly (2026-07-30)$gte+$lte(the dashboard's own window)$gte+$lte, full ISO with end-of-dayConfirmed at the storage layer:
SELECT typeof(created_date)istextfor all 38 rows, and binding the same bounds as strings in raw SQL returns 38 — so the window is right and the binding is not.Why the other three dashboards are fine: they window
close_date, aField.date(), which stays TEXTYYYY-MM-DDon both sides of the comparison. That is why Service was the outlier — not the preset choice.Upstream status
This is a platform defect, already reported:
datetime列上丢失当天数据 —— 默认配置即命中 objectstack#3777 — open,bug/priority:p1. A separate datetime bug: a bareYYYY-MM-DD$lteupper bound on a datetime column drops every record created after 00:00 that day — silently, by an amount that grows through the day.The second one matters for the restore condition: upgrading the platform alone does not make it safe to window a datetime field. Both fixes must land first, which is what the code comment and the CI guard now encode.
On this side, #520 already tracks the same root cause from the 17.0-rc upgrade angle — #460 is the 16.1.0 view of one dashboard, #520 is the platform-wide impact and the upgrade path. This PR resolves the former and leaves the latter open; measurements are cross-posted there.
What this PR does
Removes the
dateRangeblock rather than widening it. Verified in the browser: 30 open / 7 critical / 45.0h avg resolution / 3 SLA breaches, every chart and the table populated.The cost is visible and intentional: this dashboard has no date picker for now. The block is left commented out at the exact spot to restore, with both upstream issue numbers as the precondition. A picker that zeroes every widget is strictly worse than no picker.
Type of Change
Related Issues
Fixes #460
Refs #520 (same root cause, platform-wide impact and 17.0 upgrade path — stays open)
Refs objectstack-ai/objectstack#3912 (fixed upstream, 17.0 train) · objectstack-ai/objectstack#3777 (open — blocks restoring the range)
Changes Made
src/dashboards/service.dashboard.ts— remove thedateRangeblock; document the root cause, the measurements, both upstream issues, and the restore precondition in place.src/dashboards/service.dashboard.ts— document that thedaily_case_volumewidget's$gte: '{30_days_ago}'floor is inert for the same reason (a$gteon a datetime is true for every row), so that chart currently plots every case, not the last 30 days. Indistinguishable today because the seed spans exactly 30 days; it starts working when the driver is fixed. Flagged rather than papered over — the title's claim isn't enforced yet.test/metadata-references.test.ts— replaces the window-arithmetic guard from the first push, which could not detect this class of defect at all. The new guard fails if any dashboard windows adatetimefield, and a second test checks the range field actually exists on the objects its widgets aggregate..changeset/service-dashboard-default-date-range.md— patch changeset.Note on the issue's other suggestion (seed recent cases): already landed independently in #481 — the case seed dates every record relative to boot,
daysAgo(1..30). It didn't help, because the$ltebound matches nothing regardless of how recent the rows are.Not touched, deliberately: the four hardcoded
trendblocks in this file are fabricated figures, but they belong to the review pass in #500 (which turns out to cover only the executive dashboard's four of sixteen — noted there).Testing
pnpm test— 8 files, 91 tests)pnpm lint— 2 warnings, both pre-existing and unrelated:crm_quote_line_itemnaming/relationship suggestions)pnpm build, pluspnpm validateandpnpm typecheckclean)pnpm demo:reset && pnpm dev, logged in as the seeded dev admin, opened the Customer Service dashboard in Chromium. Zeros before, real numbers after; captured the analytics request/response pairs and the executed SQL for the table above.The new guard was verified in both directions: it fails with the exact message
service_dashboard: dateRange windows crm_case.created_date, a datetime field — the $lte bound matches no rows, so every widget renders emptywhen the removed block is restored, and passes without it.Screenshots
Customer Service dashboard after the fix — KPI tiles at 30 / 7 / 45.0h / 3, donut, pie and bar charts all populated. (Before: every tile
0, every chart "No rows".)Checklist
pnpm changeset) — required on every PR, or theskip-changesetlabel is applieddocs-driftpassesAdditional Notes
Two things a reviewer may want to weigh in on:
dateRangewithfilterBindings: { dateRange: false }on every widget (a visible control that does nothing — the ADR-0078 shape this repo keeps removing), or adding adate-typed mirror ofcreated_datetocrm_case(a schema change to work around a driver bug, needing hook and seed upkeep).$gtesilently matches everything,$ltesilently matches nothing — so more surfaces are likely affected. That sweep belongs to [17.0-rc][疑似平台] datetime 字段的时间窗过滤返回空集(客服仪表盘全空) #520; #3912's own report notes the platform'screated_at/updated_ataudit columns are equally affected, and those are everywhere.