Files
guestguard/frontend/pages/dashboard
Kwaku Danso 9842bd4f45 feat(tier2): host analytics — Block E
GET /events/{id}/analytics renders a viewer+ dashboard summary:
overview tiles, 30-day response sparkline, invited→opened→responded
funnel, time-to-respond histogram, plus-ones distribution, channel
attribution (utm_source), and stale-guest follow-up list. A
matching /analytics/export.csv hand-offs a flat per-guest table for
Excel + Numbers.

Backend
- Migration 0009 adds tokens.utm_source for source attribution
- AnalyticsRepo with six aggregation queries — all event-scoped, all
  returning canonical-ordered series so empty buckets still render
- Redis 60s cache in the handler, keyed by (event_id, days). Cache
  miss path returns X-Cache: miss; hit returns the JSON straight
  from Redis without re-querying Postgres
- Time-to-respond uses (rsvp.submitted_at - token.created_at) as
  the latency signal (no separate "invitation sent" timestamp yet —
  Block F will add one)

Frontend
- AnalyticsCard.vue: inline SVG sparkline + Tailwind bar charts.
  No chart.js dependency; the bundle stays lean
- Stale-guests list with opened-but-no-response highlighted
- Export CSV button issues an authed fetch + blob download

Tests
- TestAnalyticsAggregations seeds 5 guests with a known mix and
  asserts every count (overview/funnel/plus-ones/time-to-respond/
  stale) matches expected
- TestAnalyticsCSVExport: header row + per-guest rows parse cleanly
- TestAnalyticsAuthzMatrix: viewer 200, outsider 404 on both endpoints
- Full integration suite passes (109.9s)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 23:11:13 +01:00
..