Skip to content

feat(admin): usage dashboard tab + user lifecycle column (Wave 0, #662) - #668

Merged
lilyshen0722 merged 4 commits into
mainfrom
feat/admin-usage-dashboard
Jul 10, 2026
Merged

feat(admin): usage dashboard tab + user lifecycle column (Wave 0, #662)#668
lilyshen0722 merged 4 commits into
mainfrom
feat/admin-usage-dashboard

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Second Wave 0 "See & Hear" ship (#662), building on the #661 funnel endpoint (#666).

Backend — two endpoints added to routes/admin/analytics.ts

  • GET /api/admin/analytics/usage?days=N — signups/day (Mongo, bot-excluded), messages/day + distinct human posters/day (PostgreSQL, bot ids passed through so agent posts don't inflate poster counts), rolling DAU/WAU from lastActive, total human users. days clamped 7–90, admin-gated, rate-limited.
  • GET /api/admin/analytics/lifecycle — per-user {pods, messages} via one Mongo aggregate + one PG GROUP BY (no N+1). Deliberately not folded into /api/admin/users: moderation must keep working when PG is down, so the frontend merges this as a separate, failure-isolated fetch.

Frontend

  • /v2/admin/analytics (admin-gated): DAU / WAU / signups / messages / total-users cards, signups-per-day and messages-per-day inline-SVG bar charts (no chart lib), and the activation-funnel cohort table with the rate summary line. 7/30/90d range tabs. Zero-data renders stable empty charts, not a crash.
  • /v2/admin/users: the Joined column becomes Lifecyclejoined 15d · 3 pods · 42 msgs (join date preserved in the cell tooltip). Degrades to joined Nd if the lifecycle fetch fails. Cross-links between the two admin pages.

Tests

  • Backend 8/8: usage merge (Mongo signups × PG counts), bot-id passthrough to the PG query, lifecycle merge keyed by user id, days clamping, empty-cohort PG skip, 403s for non-admins on all three endpoints.
  • Frontend 3 new render tests: full data, zero data, API error → alert. All 33 v2 tests green.

Per CLAUDE.md the new layout gets a real-browser (Playwright MCP) check on live right after deploy — noting here so it's tracked; the page is admin-only so there's no anonymous exposure in the interim.

Closes #662

🤖 Generated with Claude Code

https://claude.ai/code/session_01MnRCAFgjrrGZxo9VRCmCm9

lilyshen0722 and others added 4 commits July 9, 2026 21:50
Backend (routes/admin/analytics.ts):
- GET /api/admin/analytics/usage — signups/day (Mongo, humans),
  messages/day + distinct human posters/day (PG), rolling DAU/WAU from
  lastActive, total users. days clamped 7-90.
- GET /api/admin/analytics/lifecycle — per-user pod counts (Mongo
  aggregate) + message counts (PG GROUP BY), no N+1. Kept separate from
  /api/admin/users so moderation survives a PG outage.

Frontend:
- V2AdminAnalytics at /v2/admin/analytics (admin-gated): DAU/WAU/signup/
  message/total cards, signups-per-day + messages-per-day SVG bars, and
  the #661 activation-funnel cohort table with rate summary. 7/30/90d
  range tabs. Zero-data renders stable empty charts.
- V2AdminUsers: Joined column becomes Lifecycle ('joined 15d · 3 pods ·
  42 msgs'), merged from the lifecycle endpoint as a failure-isolated
  fetch; cross-links between the two admin pages.

Tests: 8/8 backend (usage merge, bot-id passthrough to PG, lifecycle
merge, 403s), 3 new frontend render tests (data, zero-data, API error).

Closes #662

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnRCAFgjrrGZxo9VRCmCm9
lastActive was only ever set at signup (schema default) — nothing wrote
it afterward, so the funnel's returned-D1/D7 and the new DAU/WAU cards
were structural zeros for every user. Wire it in the auth middleware:
throttled to one fire-and-forget write per user per 15 minutes, with an
in-memory map bounded by active-user count. A rejected write never fails
the request and retries on the next one.

Verified against live launch data: 34 signups ≥1 day old all showed
D1=0 because of this, not because nobody returned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnRCAFgjrrGZxo9VRCmCm9
… 401 a valid request

Surfaced by pgMessages.test.js: suites that mock models/User without
updateOne made the telemetry call throw synchronously inside the auth
try-block, turning valid requests into 401s. Telemetry never gets to
break auth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnRCAFgjrrGZxo9VRCmCm9
@lilyshen0722
lilyshen0722 merged commit 2ddf54f into main Jul 10, 2026
11 checks passed
@lilyshen0722
lilyshen0722 deleted the feat/admin-usage-dashboard branch July 10, 2026 05:13
lilyshen0722 added a commit that referenced this pull request Jul 22, 2026
…r show 'never active' (#700)

Every runtime-token request (agentRuntimeAuth) left User.lastActive at
its creation-date default, so working agents rendered as never-active on
profiles/rosters (Sam noticed codex-impl 'never live' while it was
actively shipping PRs). Reuses the throttled fail-open touchLastActive
from #668 (one write per user per 15min, never fails the request) at
both agentUser assignment points (bot-user-token + legacy paths).


Claude-Session: https://claude.ai/code/session_01MnRCAFgjrrGZxo9VRCmCm9

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

Admin: usage dashboard tab (funnel, signups/day, messages/day, DAU/WAU) + user lifecycle column

1 participant