feat(analytics): typed Umami event taxonomy across the funnel
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 18s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 57s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

Add lib/analytics.ts with a single typed track() wrapper. SSR-safe,
never throws, no-ops when Umami isn't loaded. Event names form a
fixed union so refactors stay safe.

14 events wired:

  Discovery (3)
    search_submitted          FilterBar submit + near_me path
    near_me_used              all geolocation outcomes
    empty_results             search returns 0 schools

  Engagement (5)
    school_viewed             SchoolDetail + Secondary on mount, with
                              urn / phase / local_authority / from
    section_nav_used          section-nav links on both detail views
    chart_metric_changed      mobile chart chip switch
    metric_compared_in_rankings rankings metric dropdown
    external_link_clicked     Ofsted / school website / DfE (declarative
                              data-umami-event attributes)

  Conversion (5)
    compare_school_added      search/rankings/detail/compare sources
    compare_school_removed    detail toggle and compare page
    compare_viewed            once per session when there's a selection
                              (school_count, phase_mix)
    compare_metric_changed    compare page metric dropdown
    compare_shared            native sheet vs clipboard distinguished

  Operational (1)
    api_error                 caught in handleResponse, includes
                              endpoint / status / route

Suggested Goals to configure in the Umami dashboard for the funnel
report: search_submitted → school_viewed → compare_school_added →
compare_viewed → compare_shared.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Tudor Sitaru
2026-05-19 22:04:22 +01:00
parent 4cfae93a0d
commit 7e182e88b2
11 changed files with 234 additions and 10 deletions
+10
View File
@@ -65,6 +65,16 @@ async function handleResponse<T>(response: Response): Promise<T> {
// If parsing JSON fails, use the default error
}
// Client-side: report to analytics so we can spot silent failures.
// No-ops on SSR (track guards against missing window).
if (typeof window !== 'undefined') {
const { track } = await import('./analytics');
try {
const endpoint = new URL(response.url).pathname;
track('api_error', { endpoint, status: response.status, route: window.location.pathname });
} catch { /* never */ }
}
throw new APIFetchError(
`API request failed: ${errorDetail}`,
response.status,