Part of the triage of #4909 (items 1–4). All four findings live in the same ~90 lines of the shared static analytics template (analytics/analytics_package/analytics/static_site/template/index.html), so they are fixed together.
Problems
- Hardcoded stat cards (
index.html:898-921): the access-requests stats grid renders exactly three cards — dbGaP, DUOS, Total — while the serviceName()/services-Set mechanism is generic over N services. A third or unclassified service is counted in Total but gets no card, so the cards visibly fail to sum.
serviceName() fallback (index.html:365-369): returns "" for a missing click_url and the raw URL for unknown hosts, so every distinct unknown URL counts as a separate "service" in the showService gate.
- Case-sensitivity asymmetry:
serviceName() matches duos.org / dbgap.ncbi.nlm.nih.gov case-sensitively, while the Python generator filters click URLs case-insensitively (fetch.py:188, str.contains(..., case=False)). A mixed-case URL passes the generator but fails JS classification.
- Non-array guard + un-awaited render (
index.html:887 / index.html:419): the !data || data.length === 0 guard passes a non-array object, which then crashes at data.reduce; the call site is un-awaited, so the rejection bypasses loadData's error UI — silent failure.
Fix
- Make
serviceName() case-insensitive, normalize unknown hosts to hostname, and bucket missing/unparseable URLs.
- Reduce access-request rows into a service→count map and render one stat card per present service (plus Total).
- Guard with
Array.isArray, and await the call so failures surface through loadData's error handling.
Found during review of anvilproject/anvil-portal#4075; the fix belongs here in the shared template, not per-site copies.
Part of the triage of #4909 (items 1–4). All four findings live in the same ~90 lines of the shared static analytics template (
analytics/analytics_package/analytics/static_site/template/index.html), so they are fixed together.Problems
index.html:898-921): the access-requests stats grid renders exactly three cards — dbGaP, DUOS, Total — while theserviceName()/services-Set mechanism is generic over N services. A third or unclassified service is counted in Total but gets no card, so the cards visibly fail to sum.serviceName()fallback (index.html:365-369): returns""for a missingclick_urland the raw URL for unknown hosts, so every distinct unknown URL counts as a separate "service" in theshowServicegate.serviceName()matchesduos.org/dbgap.ncbi.nlm.nih.govcase-sensitively, while the Python generator filters click URLs case-insensitively (fetch.py:188,str.contains(..., case=False)). A mixed-case URL passes the generator but fails JS classification.index.html:887/index.html:419): the!data || data.length === 0guard passes a non-array object, which then crashes atdata.reduce; the call site is un-awaited, so the rejection bypassesloadData's error UI — silent failure.Fix
serviceName()case-insensitive, normalize unknown hosts to hostname, and bucket missing/unparseable URLs.Array.isArray, andawaitthe call so failures surface throughloadData's error handling.Found during review of anvilproject/anvil-portal#4075; the fix belongs here in the shared template, not per-site copies.