From eb0e6aa1730b54df7453f7a1ecdf58174b778cea Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 21 Jul 2026 00:25:09 -0400 Subject: [PATCH] feat: add PostHog + event analytics to docs site Add privacy-safe analytics to docs.openadapt.ai (mkdocs-material) alongside the native Google Analytics integration. - overrides/main.html: Material overrides partial. Emits the PostHog snippet in extrahead ONLY when NEXT_PUBLIC_POSTHOG_KEY is set at build time (hard no-op otherwise). Respects Do-Not-Track, uses person_profiles: identified_only, autocapture on. Adds a shared-taxonomy event layer (docs_search with query_length only, outbound_click host+path only) plus a location$ pageview hook for Material's navigation.instant. Every call is guarded so it never throws when analytics is disabled. - mkdocs.yml: register theme.custom_dir: overrides; make GA env-driven (GOOGLE_ANALYTICS_KEY, default G-CJ01Y19XJN); add build-time posthog_key / posthog_host env vars reusing the marketing site's names + one PostHog project. - ANALYTICS.md (repo root, not published): documents what is tracked, env vars, DNT respect, and that no PHI/PII is involved. Verified: mkdocs build --strict, pytest tests/, and validate_docs.py all green. No PostHog script in built HTML when the key is unset; present when NEXT_PUBLIC_POSTHOG_KEY=phc_test. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM --- ANALYTICS.md | 54 ++++++++++++++++++ mkdocs.yml | 16 ++++++ overrides/main.html | 130 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 ANALYTICS.md create mode 100644 overrides/main.html diff --git a/ANALYTICS.md b/ANALYTICS.md new file mode 100644 index 0000000..c1985b3 --- /dev/null +++ b/ANALYTICS.md @@ -0,0 +1,54 @@ +# Analytics on docs.openadapt.ai + +This documentation site uses privacy-safe, build-time analytics. This file lives +at the repository root (not under `docs/`) so it does not affect the published +site or `mkdocs build --strict` navigation. + +## What is tracked + +The site reports into two destinations that share one event taxonomy across all +OpenAdapt web properties: + +- **PostHog** (product analytics), and +- **Google Analytics 4** (Material's native integration). + +Tracked events: + +| Event | Properties | Notes | +| --- | --- | --- | +| `$pageview` | (automatic) | PostHog fires this via Material's `location$` observable so it works under `navigation.instant`. GA pageviews are handled natively by Material. | +| `docs_search` | `query_length` | Only the LENGTH of the search query is captured, never the raw text. Debounced by ~800ms. | +| `outbound_click` | `destination`, `href` | `destination` is one of `app`, `download`, or `github`. `href` is host + path only, with the query string stripped. | + +Search and pageview events are also emitted to Google Analytics where Material's +native integration does not already cover them. + +## Shared project + +Both destinations are shared with the marketing site. PostHog reuses the same +project (via the same env var names below), so all OpenAdapt properties report +together. + +## Environment variables (set at BUILD time) + +Analytics is a static build, so keys are read when `mkdocs build` runs, not at +page-serve time. Set these in the docs deploy/CI environment: + +- `NEXT_PUBLIC_POSTHOG_KEY`: PostHog project key. Reuse the SAME key as the + marketing site so everything lands in one PostHog project. When this is unset, + no PostHog script is emitted into the built HTML at all (a hard no-op). +- `NEXT_PUBLIC_POSTHOG_HOST`: PostHog API host. Defaults to + `https://us.i.posthog.com`. +- `GOOGLE_ANALYTICS_KEY`: GA4 measurement ID. Optional; defaults to the existing + `G-CJ01Y19XJN`. GA measurement IDs are public (non-secret). + +## Privacy properties + +- **No PHI or PII.** These are public documentation pages only. +- **Do-Not-Track is respected.** When the browser sends a Do-Not-Track signal, + PostHog is never loaded or initialized. +- **Conservative capture.** Search records only `query_length` (never the raw + query), and outbound links record only host + path (never the query string). +- **PostHog `person_profiles: 'identified_only'`** so anonymous visitors do not + create person profiles. +- **No secrets committed.** Keys are provided only through build-time env vars. diff --git a/mkdocs.yml b/mkdocs.yml index 228c019..fbccea9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,9 @@ copyright: >- theme: name: material + # Register the local overrides directory so overrides/main.html can inject the + # PostHog snippet and shared analytics event layer via the extrahead block. + custom_dir: overrides logo: assets/logo.svg favicon: assets/favicon.ico # Match the public site's local system stacks and avoid a third-party font @@ -85,6 +88,19 @@ markdown_extensions: extra: generator: false + # Analytics. See ANALYTICS.md (repo root) for the full data-handling notes. + # Google Analytics is env-driven; the fallback keeps the existing measurement + # ID working when GOOGLE_ANALYTICS_KEY is unset. GA measurement IDs are public + # (non-secret), so committing the fallback is fine. + analytics: + provider: google + property: !ENV [GOOGLE_ANALYTICS_KEY, "G-CJ01Y19XJN"] + # PostHog is read at BUILD time. When NEXT_PUBLIC_POSTHOG_KEY is unset (local + # builds and CI), posthog_key is "" and overrides/main.html emits no PostHog + # script at all. Reuses the same env var names and host as the marketing site + # so every property reports into one PostHog project. + posthog_key: !ENV [NEXT_PUBLIC_POSTHOG_KEY, ""] + posthog_host: !ENV [NEXT_PUBLIC_POSTHOG_HOST, "https://us.i.posthog.com"] social: - icon: fontawesome/brands/github link: https://github.com/OpenAdaptAI diff --git a/overrides/main.html b/overrides/main.html new file mode 100644 index 0000000..18af438 --- /dev/null +++ b/overrides/main.html @@ -0,0 +1,130 @@ +{% extends "base.html" %} + +{# + Privacy-safe analytics for docs.openadapt.ai. + + Google Analytics is wired through Material's native `extra.analytics` block + (see mkdocs.yml), which already handles pageviews and the built-in `search` + event, including under `navigation.instant`. This partial adds PostHog and a + small shared-taxonomy event layer on top. + + The PostHog script is emitted only when NEXT_PUBLIC_POSTHOG_KEY is present at + BUILD time (mkdocs `!ENV` substitution). When the key is unset (local builds + and CI), the entire PostHog block is absent from the generated HTML: a hard + no-op, not a disabled runtime path. +#} +{% block extrahead %} + {{ super() }} + + {% if config.extra.posthog_key %} + + + {% endif %} + + + +{% endblock %}