Skip to content

[AAASM-3558] 📈 (docs): Advanced Consent Mode so GA fires + always-visible feedback#170

Merged
Chisanan232 merged 5 commits into
masterfrom
v0.0.1/AAASM-3558/advanced_consent_mode
Jun 23, 2026
Merged

[AAASM-3558] 📈 (docs): Advanced Consent Mode so GA fires + always-visible feedback#170
Chisanan232 merged 5 commits into
masterfrom
v0.0.1/AAASM-3558/advanced_consent_mode

Conversation

@Chisanan232

Copy link
Copy Markdown
Contributor

Description

On the published python-sdk docs site, Google Analytics never fired for a normal visitor — window.gtag was undefined and zero /g/collect hits occurred on a content page. mkdocs-material uses basic Consent Mode: it loads gtag.js and wires the feedback widget only inside its generated __md_analytics(), which runs only after the analytics consent cookie is granted. Our consent config is opt-in (cookies.analytics.checked: false, off by default), so GA was never loaded at all.

This PR brings python in line with the other four doc sites (node, hub, core, go), which all use advanced Consent Mode v2: gtag.js loads on every page with analytics_storage: 'denied' default (cookieless modelled pings until consent, full hits after), so GA always works and is detectable.

What changed (theme overrides under docs/_overrides, no source/runtime code):

  • Override partials/integrations/analytics/google.html — keep Material's __md_analytics() body (gtag loader + config + feedback wiring) but assign window.gtag (so the tag is detectable), emit Consent Mode v2 default denied for analytics_storage/ad_storage/ad_user_data/ad_personalization before config, and restore granted from Material's stored consent (__md_get('__consent').analytics).
  • Override partials/integrations/analytics.html — call __md_analytics() unconditionally (Consent Mode now handles denial), instead of only when consent is granted. gtag is loaded exactly once.
  • The feedback widget is revealed + wired inside __md_analytics(), so it is always visible and its single consent-gated GA feedback event flows through Material's own handler — no custom handler, no double-count. (Folds in the AAASM-3557 / PR [AAASM-3557] 🐛 (docs): Make feedback widget always visible + fix version-banner JS #169 feedback goal.)
  • Carry over the two JS fixes from the closed PR [AAASM-3557] 🐛 (docs): Make feedback widget always visible + fix version-banner JS #169: outdated-banner new URL("{{ base_url }}", location) crash fix, and the versions.json deep-page path fix (resolve from base_url deploy root).
  • Tag the mike root-redirect stub: a custom redirect template carrying the gtag snippet, passed via mike set-default --template, so Google's tag test detects GA on the bare-root /python-sdk/ too.

Type of Change

  • ✨ New feature
  • 🔧 Bug fix
  • ♻️ Refactoring
  • 🍀 Performance improvement
  • 📚 Documentation update
  • 🚀 Release

Breaking Changes

  • No
  • Yes (please describe below)

GDPR posture note: this changes python from "no GA data without consent" to "cookieless, modelled data until consent" (Consent Mode v2 denied-default) — consistent with the already-shipped posture on the other four doc sites. No raw analytics cookies are set until the visitor grants consent.

Related Issues

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No tests required (theme override; verified live in a real browser instead)

uv sync --group docs then uv run mkdocs build --strict builds clean. The built site was served and driven with Playwright's bundled Chromium (require('playwright').chromium) on a deep content page (/concepts/architecture/):

With NO consent:

  • typeof window.gtag === "function"
  • 1 /g/collect hit fires — gcs=G100 (cookieless, analytics+ad denied), en=page_view, tid=G-L8JTJDG8G7
  • Feedback widget visible (form not hidden, 👍/👎 both present) ✅
  • Click 👍 → thank-you note shown; exactly 1 feedback event in dataLayer (delta = 1, not 2 — no double-count) ✅

After granting analytics consent (seed Material's scoped <root>.__consent = {"analytics":true}, reload):

  • typeof window.gtag === "function"; Consent Mode updates to analytics_storage: 'granted'
  • Click 👎 → exactly 1 feedback event delta; pre-filled GitHub issue link present ✅

No Failed to construct 'URL' error anywhere (the only console errors on that page are a pre-existing mermaid-diagram parse error + 2 unrelated 404 resources, not introduced here). The versions.json deep-page fix resolves from base_url (deploy root) — on a deep page base_url=../../versionsUrl = <deploy-parent>/versions.json, i.e. one level above the version dir at any depth (the old code popped only the page's last segment and 404'd).

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Comments added for complex logic
  • Documentation updated if needed
  • All tests passing (build --strict clean; live browser verification above)

🤖 Generated with Claude Code

Chisanan232 and others added 4 commits June 23, 2026 09:55
Override Material's analytics partials so gtag.js loads on every page
with analytics_storage denied by default (cookieless pings until
consent, full hits after), restoring granted state from stored consent
and assigning window.gtag so the tag is detectable. The feedback widget
is revealed and its single consent-gated GA `feedback` event still flows
through Material's own handler (no double-count).

Refs AAASM-3558.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Override Material's outdated.html so the script passes `location` as the
base to `new URL("{{ base_url }}", location)`. Material's generated code
calls `new URL("..")` with no base, which throws
`TypeError: Failed to construct 'URL'` on every page load.

Refs AAASM-3558.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The version-warning banner derived versions.json by popping the page's
last path segment, which 404s on deep pages. Resolve it from `base_url`
(the deploy root) instead, so it always points one level above the
version directory regardless of page depth.

Refs AAASM-3558.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a custom mike redirect template with a self-contained gtag.js
snippet (advanced Consent Mode v2, cookieless-until-consent) and pass it
to `mike set-default --template` so Google's tag test detects GA on the
bare-root URL (/python-sdk/) too.

Refs AAASM-3558.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Chisanan232

Copy link
Copy Markdown
Contributor Author

Claude Code — review result

CI green — all 4 checks pass (Analyze python, CodeQL, Build documentation); mkdocs build --strict clean. MERGEABLE. No fails to ignore.

Scope vs AAASM-3558 — all parts covered, theme-overrides only, no new dependency:

  1. Advanced Consent Mode (the core fix). partials/integrations/analytics.html drops mkdocs-material's consent gate and calls __md_analytics() unconditionally; the overridden analytics/google.html mirrors material 9.7.6's __md_analytics() but (a) assigns window.gtag — material's internal pusher never did, which is exactly why python's tag was undetectable — and (b) sets Consent Mode v2 default-denied for all four storage types before config, then restores granted from stored consent. So gtag loads on every page (cookieless pings when denied, full after consent). ✅
  2. Feedback stays single-sender. Material's own handler (inside __md_analytics) sends the one feedback event; the PR correctly did not re-add [AAASM-3557] 🐛 (docs): Make feedback widget always visible + fix version-banner JS #169's main.html handler (that would double-count). ✅
  3. new URL("..", location) outdated-banner fix + versions.json from base_url deep-page fix carried over. ✅
  4. mike root-redirect stub tagged via a custom redirect template (scripts/ci/templates/) wired through mike set-default. ✅

Live-verified (Chromium, deep page /concepts/architecture/):

  • No consent → typeof window.gtag === "function"; 1 /g/collect hit (gcs=G100 cookieless, page_view, tid=G-L8JTJDG8G7); feedback widget visible; 👍 → exactly 1 feedback event (delta 1, not 2).
  • Consent granted → Consent Mode updates to analytics_storage: granted; 👎 → exactly 1 event, issue link present.
  • No Failed to construct 'URL' error.

Two honest notes (both correct calls): the mike root-stub tag lands on release deploys (where mike set-default sets the root redirect anyway), not on master-push latest deploys; SRI skipped (unversioned Google loader, matches posture). Supersedes AAASM-3557 / #168 / #169.

Ready for approval + merge. (Post-merge: re-verify on the live versioned site that window.gtag loads + a /g/collect hit fires without consent — the mike-versioned root/versions.json paths can't be fully reproduced in a plain local build.)

… hide on reject)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Chisanan232

Copy link
Copy Markdown
Contributor Author

♻️ Gate feedback widget on analytics consent

Per owner decision, the feedback 👍/👎 widget is now gated on analytics consent — visible only after the user Accepts analytics cookies; hidden on Reject and while undecided. GA loading/reporting is left unchanged (still loads + sends cookieless pings for everyone — GA itself is not gated).

Change

In the overridden __md_analytics() (docs/_overrides/partials/integrations/analytics/google.html), the document$.subscribe feedback block now early-returns — leaving the form hidden and not wiring click handlers — unless stored consent (__md_get("__consent")) has .analytics truthy:

var __aa_fb_consent = __md_get("__consent");
if (!(__aa_fb_consent && __aa_fb_consent.analytics)) return;

The GA loader + gtag('config', …) + Consent Mode defaults still run unconditionally above this block, so GA is unaffected. mkdocs-material reloads on consent submit/reset, so reading stored consent at load time is sufficient — the widget appears right after Accept and stays hidden after Reject. No second event sender; material's single feedback handler is retained (no double-count). The new URL/versions.json/mike-stub changes in the PR are untouched.

Validation

uv sync --group docsuv run mkdocs build --strict builds clean (no errors). Served locally and driven with bundled playwright chromium on a deep content page (/python-sdk/compatibility/runtime/); scoped consent key is /python-sdk/.__consent.

State feedback form.md-feedback hidden window.gtag /g/collect hit (GA live) Consent Mode analytics_storage feedback click
Undecided (no __consent) hidden function 1 hit ✓ denied n/a
Accept ({"analytics":true}) visible function 1 hit ✓ granted exactly 1 feedback gtag event ✓
Reject ({"analytics":false}) hidden function 1 hit (cookieless) ✓ denied n/a

GA fires a /g/collect page_view in all three states; the feedback form only reveals + wires handlers under Accept, and a single click emits exactly one gtag('event', 'feedback') (material's own handler — no double-count).

Commit: a422f64♻️ (docs): Gate feedback widget on analytics consent (show on accept, hide on reject)

🤖 Generated with Claude Code

@Chisanan232 Chisanan232 merged commit daf93e9 into master Jun 23, 2026
6 checks passed
@Chisanan232 Chisanan232 deleted the v0.0.1/AAASM-3558/advanced_consent_mode branch June 23, 2026 02:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant