Commit 64df5eb
Fix/participant count consistency (#29)
* feat: add duration/participants/issues summary endpoints (Phase 2 + 3)
Five new endpoints for the remaining dashboard charts that fetch raw data:
- GET /v1/conferences/duration-summary
Returns conference counts bucketed by duration range (< 1m, 1-3m, etc.)
- GET /v1/conferences/participant-count-summary
Returns distribution of conferences by participant count
- GET /v1/issues/summary
Returns issue counts grouped by code with titles
- GET /v1/issues/gum-summary
Returns getusermedia_error issue counts grouped by error name
Also adds three new filter params to /v1/conferences for click-to-detail
modals on these charts:
- duration_gte, duration_lt (for duration chart)
- issue_code (for most-common-issues chart)
All endpoints accept appId, created_at_gte, created_at_lte and handle
both Python native ISO format and JavaScript's toISOString Z suffix.
Phases 2 and 3 of #20 — eliminates the need for the dashboard to
download all conferences (~38MB) and all issues (~73MB).
* feat: add connections + sessions summary endpoints (Phase 4 + 5 of #20)
Adds three new aggregation endpoints that let the dashboard stop
downloading full /connections and /sessions payloads to build charts
client-side:
- GET /v1/connections/summary — relay vs direct connection counts
(replaces the Relayed-connections pie chart's client-side reduce)
- GET /v1/connections/setup-time-summary — connection setup-time
buckets with per-bucket conference_ids for click-to-detail
- GET /v1/sessions/summary — browsers, OS, country, and city/geo
aggregates (powers Browsers, OS, and Map charts in one roundtrip)
Also accepts `conference_ids=a,b,c` on /conferences so the setup-time
chart can page through matched conferences on click.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: 60s Redis cache + pre-warm for dashboard summary endpoints (Phase C of #20)
With Phases 0-5 merged, every dashboard chart reads from a server-side
aggregation endpoint. The SQL is fast with indexes, but the same ~8
queries run on every page load, and the heavy ones (sessions.summary,
connections.setup_time_summary) still cost 400-800ms on a live tenant.
Adds a thin caching layer in front of each summary view:
- `app/summary_cache.py` — `cached_json(endpoint, request, compute)`
hashes (endpoint + filter params) into a short key, reads Redis,
falls through to `compute()` on miss, and writes back with a 60s TTL.
Redis failures are tolerated (settings already has IGNORE_EXCEPTIONS).
- Each of the eight summary views moves its existing compute body into
a local `compute()` closure and returns through the helper. No change
to the JSON shape, query logic, or error handling.
- `manage.py prewarm_summaries` — scheduled command that iterates apps
with recent traffic (default: any conference in the last 2 days) and
runs every summary view with the 30d-window filters the dashboard
sends by default. Intended to run every ~30s as an ECS scheduled
task so first visitors never see a cold miss.
Measured locally against a 7-day Production clone (~18k conferences /
38k sessions / 38k connections):
endpoint cold warm
conferences/summary 391ms → 12ms (33x)
sessions/summary 748ms → 11ms (68x)
connections/setup_time_summary 373ms → 11ms (34x)
conferences/participant_count_summary 216ms → 7ms (31x)
issues/gum_summary 107ms → 6ms (18x)
connections/summary 57ms → 6ms (9.5x)
issues/summary 45ms → 86ms (noise; both <100ms)
conferences/duration_summary 19ms → 8ms (2.3x)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: dedupe conferences for issue_code; harden gum-summary
* test: summary_cache LocMem tests and prewarm smoke
- Unit-test cache key rules, hit/miss, TTL override, and soft-fail on get/set errors.
- Smoke-test prewarm_summaries for zero apps and one recent app (8 views).
Made-with: Cursor
* fix: bucket created_at_gte/created_at_lte to the minute in cache key
The dashboard sends `new Date().toISOString()` minus 30 days as
created_at_gte (web app.vue:189), which is millisecond-precise. With the
unrounded value going straight into _make_key, every page load — even
back-to-back reloads — produced a unique SHA1 digest and a fresh cache
miss, so the warm path never served real users:
flush redis -> 0 keys
load dashboard -> 8 keys
reload -> 16 keys (8 stale + 8 fresh)
prewarm_summaries had the same problem on the write side: its own
since_window = utcnow() - 30d advanced every run, so the entries it
populated never matched what the dashboard requested.
Truncate ISO timestamps to the minute (YYYY-MM-DDTHH:MM) before hashing,
so two requests in the same wall-clock minute share an entry. Correctness
still holds because the 60s TTL bounds staleness regardless of bucket
size.
After the fix, two same-minute dashboard loads both produce 8 keys (no
growth), and the 2nd load's slow endpoints serve from cache:
before after
/v1/sessions/summary 2314ms -> 411ms
/v1/connections/setup-time-summary 1117ms -> 229ms
/v1/conferences/summary 994ms -> 48ms
Two regression tests added covering the bucket and the minute boundary.
* Fix conference participant counts for dashboard and list
Count only active non-SFU participants with a session on the conference,
excluding peer stubs without sessions and SFU endpoints. Shared annotation
in conference_query.py used by conferences list and participant-count summary.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix participants_count when filtering conferences by participantId
Use a correlated Subquery instead of Count on the outer participants join.
Filtering by participantId narrowed that join to one person, so the
aggregate incorrectly showed 1 for multi-participant calls. The subquery
counts independently. Add regression test.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Clarify participantId filter regression test docstring
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Samson Nkrumah <samsonnkrumah253@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent b6180bb commit 64df5eb
4 files changed
Lines changed: 71 additions & 25 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | | - | |
| 5 | + | |
7 | 6 | | |
8 | 7 | | |
9 | 8 | | |
10 | 9 | | |
11 | | - | |
12 | 10 | | |
13 | 11 | | |
14 | 12 | | |
| |||
50 | 48 | | |
51 | 49 | | |
52 | 50 | | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
| 51 | + | |
64 | 52 | | |
65 | 53 | | |
66 | 54 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
11 | | - | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
| 66 | + | |
75 | 67 | | |
76 | 68 | | |
77 | 69 | | |
| |||
0 commit comments