Skip to content

Add usage metrics section to admin panel#171

Merged
jrchatruc merged 12 commits into
mainfrom
feat/usage-metrics-admin
Apr 24, 2026
Merged

Add usage metrics section to admin panel#171
jrchatruc merged 12 commits into
mainfrom
feat/usage-metrics-admin

Conversation

@jrchatruc

Copy link
Copy Markdown
Collaborator

Summary

  • New Usage Metrics section at the top of the Admin page
  • Summary cards: active users (last N days), total users, tasks created / completed / failed, total cost, total tokens
  • Stacked bar chart of tasks per day, colored by completed / failed / in progress
  • Side-by-side tables of top 10 users and top 10 repos by task count + cost
  • Period selector (7 / 30 / 90 days)

Backend

  • New module `metrics.rs` with four admin-only endpoints:
    • `GET /api/admin/metrics/summary`
    • `GET /api/admin/metrics/tasks-over-time`
    • `GET /api/admin/metrics/top-users`
    • `GET /api/admin/metrics/top-repos`
  • Active users are counted from `audit_log` `auth.login` events (distinct `actor`) in the period
  • All other metrics aggregate directly from the `tasks` table

Test plan

  • As admin, visit /admin — verify the new section renders above Users
  • Change the period selector and confirm numbers update
  • Check the bar chart tooltips show counts per day
  • Confirm top users and top repos are sorted by task count
  • As a non-admin, verify the endpoints return 403 and the section is not reachable (non-admin is already redirected off /admin)

Shows active users, task activity, cost totals, and top users/repos
over a selectable period (7/30/90 days). Admin-only.

Backend: new /admin/metrics/{summary,tasks-over-time,top-users,top-repos}
endpoints that aggregate from tasks, users, and audit_log tables.

Frontend: new UsageMetricsSection at the top of the Admin page with
summary cards, a stacked bar chart of daily task activity, and top
users/repos tables side by side.
The Usage Metrics card also contains 'Users' text (Active Users, Top
Users, etc), so the weak hasText: 'Users' filter was matching both
cards and causing strict mode violations on testadmin cell lookups.
Promote metrics from an admin-only Admin card to a top-level /metrics
tab visible to every signed-in user, with a redesigned UI that surfaces
period-over-period trends so activity growth/shrinkage is obvious.

- Backend: swap AdminUser for AuthUser on metrics handlers and move
  routes from /api/admin/metrics/* to /api/metrics/*. Summary now
  returns previous-period counts for active users, tasks, and cost so
  the UI can render trend deltas. tasks-over-time uses generate_series
  to fill zero-activity days and adds per-day cost for the overlay.
- Frontend: new Metrics page with accent stat cards (trend badges that
  flip red when cost rises), a stacked bar + smoothed cost-line chart
  with dashed grid and dual axes, top users/repos as progress-bar rows,
  and a headline comparing the first vs second half of the window. New
  sidebar entry and route; old UsageMetricsSection removed from Admin.
The bezier-smoothed cost line was producing huge spurious peaks and
dips around isolated spike days, and amber dots piled up along the
zero baseline on every no-cost day. The left-axis ticks were also
rounding 1.25/2.5/3.75 to 1/3/4, producing uneven spacing like 0,1,3,4,5.

- Draw the cost line with straight segments so it cannot overshoot
  between points.
- Only render dots on days with nonzero cost.
- Thin the line slightly and keep the gradient area fill.
- Build y-axis ticks with a nice-step algorithm and force integer
  unit steps when the max is small.
…as worked on

The stacked-status bars read as noisy rectangles on sparse data, so
replace them with a simple tasks line + cost line on dual axes.
Also redefine "activity" so a task counts for every day it was touched
— not just the day it was created — which makes long-running tasks
show up on every day of real work.

- Chart: tasks area + line in emerald on the left axis; cost line in
  amber on the right axis; straight-line interpolation and dots only
  on days with real values, so the chart stops inventing spikes.
- Backend tasks-over-time: union tasks.created_at, task_messages, and
  task_logs, then COUNT(DISTINCT task_id) per day. Cost is attributed
  to each task's updated_at day (that's when the cost value is
  actually observed).
- Drop now-unused completed/failed fields from TasksOverTimeRow and
  the JSON response.
Explain that a log line on a given day only appears when either a user
sent a new message that day or the agent was still running through a
long-running task on that day — both legitimately "worked on" — so
they are intentionally included in the union.
Native <title> tooltips are OS-styled and slow to appear. Replace them
with a styled overlay that positions itself next to the hovered day's
point, plus a vertical guide line and emphasized point markers so it's
obvious which day is being inspected.

- Invisible per-day hit rects drive a hoverIdx state
- Tooltip shows full date, task count, and cost
- Guide flips to the left side when hovering near the right edge
- Hit rects are drawn last so they capture events over lines and points
Previously cost was attributed to each task's updated_at day only. That
made the cost line wildly out of sync with the activity line: a day
showing tasks=4 would still tooltip as Cost $0.0000 because those four
tasks were each "last updated" on some other day.

Split each task's total_cost_usd evenly across the in-window days it
was actually worked on. A task worked on three days contributes a
third of its cost to each of those days, so the cost curve tracks the
activity curve and the hover tooltip reads sensibly.

Also filter the activity union CTE to the query window to avoid
scanning the whole task_logs table.
Coverage thresholds (functions/branches at 70%) failed on CI because
the new Metrics page had no e2e tests exercising its code paths. Add a
metrics.spec.ts that visits the page as admin/member/viewer, flips the
period selector to 7/30/90 days, renders the chart and its legend,
covers the Top Users / Top Repos cards with seeded users and repos,
navigates via the sidebar link, and moves the mouse through the chart
to exercise the hover-state handlers.
[class*="card"] matched both the outer <div data-slot="card"> and the
inner <div data-slot="card-header">, so filter().toBeVisible() failed
in strict mode with "resolved to 2 elements". Switch to the exact
[data-slot="card"] selector so each card filter matches a single
wrapper.
@jrchatruc jrchatruc merged commit 2d9222d into main Apr 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant