feat(overview): one range selector across every panel - #90
Conversation
…beys The Overview stacks five panels on three different time windows: KPIs and Costs on a hardcoded 30 days, History on 30 days computed in the page, Sessions and Models on all time with no filter available, Tools on the range key it already accepts. Only the KPI labels name their window, and they do it as a literal "(30d)" in the string. Extend the range contract from ADR-0011/0012 to /overview, /sessions, /costs and /models rather than translating in the frontend (which cannot scope three of them at all, and would answer year/all from spans alone after retention has deleted them) or adding a second aggregated endpoint that would drift from the pages each section links to. Defaults preserve current behaviour instead of converging: month for /overview and /costs, all for /sessions and /models. Explicit from/to beats range on /costs. Long ranges resolve against the spans ∪ daily_usage union. The sessions list cannot reconstruct rows for rolled-up days, so it clamps to raw coverage and reports covered_since rather than absorbing the shortfall. Co-Authored-By: Daedalus <daedalus@agents.flopbut.local> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… models The dashboard's read endpoints disagreed on what "now" means: /overview was hardcoded to 30 days, /sessions and /models had no time filter at all, and /costs defaulted to 30 days via from/to. A single Overview range selector is unsatisfiable while those four windows are independent. All four now accept `range` with the same five keys, the same fallback-don't-400 rule, and the same spans-union-daily_usage resolution as /users and /tools. Defaults preserve today's behaviour rather than converging: /overview and /costs default to month, /sessions and /models to all, so no existing caller's meaning changes. Explicit from/to on /costs still beat the range key. Long ranges resolve over the shared usageCTE, the ADR-0011 raw-floor split, so year and all keep answering after retention has deleted the raw spans instead of silently repeating the month figure. The sessions list is the one panel that cannot come from the roll-up - daily_usage keeps no start time, model or status - so it stays raw-only, clamps, and reports covered_since rather than absorbing the shortfall. Also fixes users_count, which ignored both the range and the user_id filter, and counted the anonymous bucket as zero principals rather than one. Co-Authored-By: Wayland <wayland@agents.flopbut.local> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Overview was five windows stacked vertically: the KPIs showed 30 days, the Sessions and Models blocks showed all time, History and Costs 30 days each by their own arithmetic, and only the KPI labels said which — as a literal (30d) baked into the string. A reader comparing the Sessions KPI against the Models table below it was comparing 30 days against all time. The header now carries one SegmentedControl, persisted under its own cotel_overview_range cookie so it cannot move the Users or Tools page, and every section takes the selection: Users, History (hour granularity on Day, day otherwise), Costs, Tools, Models, Sessions. KPI labels take their suffix from RANGE_SUFFIX; All renders none. A new Users block leads the stack with the top 5 principals by spend, answered by the existing /users list rather than a new endpoint. Sessions moves to the bottom, as the one block that cannot honour a long range, and states the window it actually covers when the server clamps it. The Costs block drops its inner by-model table — the Models block below it is the same data at full width. The user-search typeahead is gone and UserSearch with it; nothing else imported it. ?user_id= still scopes the page, and now says so: a chip in the header names the user and clears the scope on click, instead of a silently filtered page. Co-Authored-By: Wayland <wayland@agents.flopbut.local> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sessions Two disagreements survived the range rework, both found by driving the page against a copy of production after a real retention roll-up. /history reads raw spans only and has no range parameter, so on a long range it charted a visibly shorter window than the Costs panel beside it with nothing saying so — the exact defect the single selector exists to remove. Its coverage starts at the same raw floor the sessions list already reports, so the page now states it in one line, the same way the Sessions block does. The two share one request: same SWR key, not a second fetch. /sessions also counted a span with an empty session_id as a session of its own. That produced a list row whose link 404s, and put the Sessions page one ahead of the Overview's session count for the same window. An empty session_id is not a session — it is what the roll-up records as the unknown sentinel — so both the count and the list now exclude it. Co-Authored-By: Wayland <wayland@agents.flopbut.local> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (15)
📝 WalkthroughWalkthroughThe change adds shared range support to summary APIs, combines raw and daily usage data, updates the Overview dashboard to use one range selector, replaces user search with URL scoping, and adds API tests and documentation. ChangesOverview range unification
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant APIClient
participant Overview
participant internal_api_handler
participant spans
participant daily_usage
APIClient->>Overview: select range
Overview->>internal_api_handler: request overview data with range and user_id
internal_api_handler->>spans: query raw usage
internal_api_handler->>daily_usage: query rolled-up usage
spans-->>internal_api_handler: raw usage rows
daily_usage-->>internal_api_handler: daily usage rows
internal_api_handler-->>Overview: range-scoped metrics and coverage
Overview-->>APIClient: render dashboard sections and labels
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Squash-merged to main locally as |
|
Reopened. Context for anyone reading this thread cold: Why it got closed: this repo merges default-branch PRs by squashing locally (identity rule — a server-side What that cost us: CodeRabbit started reviewing at 21:39 and the PR was closed at 21:42 — three minutes later. Its review aborted with "Review failed: The pull request is closed." All that survived was the walkthrough. There were no findings, because no review ever ran. State now: branch restored at Requesting the full review now. Anything it finds gets fixed as new commits on this branch, and those commits are the delta that lands on |
|
@coderabbitai full review |
|
|
CodeRabbit is rate-limited — In the meantime, my own pass over the diff (15 files, +1242/-397). Ticket points — all five present in the code:
Every section handles loading, error and empty separately — no blank panels. The one thing that looked like a bug, and why it is not. Also spot-checked and clean: the CTE argument order matches placeholder order (raw filter, then aggregate), and the aggregate side is bounded Two notes, neither blocking:
Nothing here blocks. Holding the PR open for the CodeRabbit pass. |
|
Tip For best results, initiate chat on the files or code changes.
Your checks support the The two noted semantic differences are clear:
The full automated review remains pending until the rate-limit window allows a new request. ✏️ Learnings added
You are interacting with an AI system. |
The KPI reads COUNT(DISTINCT user_id) over the selected range, so it counts principals with usage in that window. The Users page lists registered users whether or not they were active, so the two totals legitimately differ and the bare "Users" label invited reading them as the same number. Co-Authored-By: Daedalus <daedalus@agents.flopbut.local> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Pushed The users KPI now reads "Active Users". It is Also added the KPI to the field table in Verified: Not fixed here, deliberately: every CodeRabbit: still rate-limited (window opens ~22:43 UTC). This push did not pull a review in with it, as the limit gates pushes too. Re-triggering once the window opens — the review will then run against |
|
Superseded — closing without merge. The range-selector work from this branch shipped to One commit here never landed — Nothing else on this branch is missing from |
Rebuilds the Overview page around a single time-range selector, and extends the
rangeAPI contract so every panel can honour it. Implements ADR-0014, building on ADR-0011 and ADR-0012.API
rangeis now accepted on four endpoints, with defaults chosen so no existing caller's meaning changes:/api/v1/overviewmonthrangeis echoed/api/v1/sessionsallcovered_since(RFC3339 ornull)/api/v1/costsmonthfrom/towin andrangeis ignored/api/v1/modelsallrangeusers_counton/overviewpreviously ignored both the range and theuser_idfilter (SELECT COUNT(DISTINCT user_id) FROM spans, no clause) — it now honours both.Long ranges (
year,all) resolve against thespans∪daily_usageunion at the ADR-0011 raw-floor split, reusing therangeUsageCTEpattern frominternal/storage/users.go. Answeringyearfromspansalone after retention has purged them is precisely the defect ADR-0011 exists to prevent. The sessions list is the one metric that genuinely cannot come from the union —daily_usagecannot reconstruct a session's start time, model or status — so it clamps to the raw floor and reports the real window viacovered_sincerather than silently absorbing the gap. Unrecognisedrangevalues fall back to the endpoint default and never 400.Frontend
SegmentedControlin the header, persisted throughuseRangeCookie('cotel_overview_range')(its own key), beside theRefreshIndicator— matching the Users page toolbar. Every section obeys it, including Tools which previously hardcoded'all'.useUsersPage— no new endpoint.by_modeltable; the Models section below is the same data at full width.RANGE_SUFFIXinstead of a hardcoded(30d);Allrenders no suffix.UserSearchis deleted (nothing else imported it), but?user_id=keeps working: the page shows a clearable indicator of who it is scoped to and keeps passinguser_idto every section.Tests
internal/api/overview_range_test.goseeds spans anddaily_usagerows straddling the raw floor and asserts:day/week/monthare answered from raw spans;year/allinclude the aggregate rows; the floor day is not double counted from both sides;rangeanduser_idcompose; and/sessionsand/modelswith norangereturn exactly what they return today.Verification
CGO_ENABLED=1 go test ./...ingolang:1.23-bookworm— green, all 9 packages.go vet ./...clean. (No native Go toolchain on the build box; CI is authoritative.)npm ci && npm run build(tsc && vite build) clean infrontend/.npm run buildso thego:embeded assets were current, then drove/with playwright-core against/usr/bin/chromium, on a 113 MB copy of production first run through a real retention roll-up (138,420 of 167,119 raw spans purged, so everything before 2026-07-21 survives only as aggregates). The numbers move and the long ranges hold: All/Year 1246 sessions / $1502.90, Month 127 / $6.16, Week and Day 0. Answered fromspansalone,yearwould have collapsed onto themonthfigure — the ADR-0011 defect./sessionsclamps to 252 rows and reports itscovered_since.Risk
Medium — four public endpoints gain a parameter and three gain response fields, and this is the dashboard's front door. No schema, migration, auth, billing or CI changes.
Implemented by Wayland; reviewed and approved by Daedalus at
13e949a.Summary by CodeRabbit
New Features
Bug Fixes
Documentation