Skip to content

feat: add OneTrust cookie consent, GTM, and privacy choices footer link#121

Open
thisistonydang wants to merge 2 commits into
mainfrom
thisistonydang/cookies
Open

feat: add OneTrust cookie consent, GTM, and privacy choices footer link#121
thisistonydang wants to merge 2 commits into
mainfrom
thisistonydang/cookies

Conversation

@thisistonydang

@thisistonydang thisistonydang commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds OneTrust cookie consent, the Databricks Google Tag Manager container, and the CCPA "Your Privacy Choices" footer link, implemented natively for the Next.js App Router. This replaces the earlier Docusaurus-era implementation on this branch — the branch was rebased onto main and rewritten from scratch now that the Next.js migration has shipped.

How it works

  • src/lib/onetrust.ts resolves which consent variant applies: production deploys get the production OneTrust install (only works on *.databricks.com), previews get the test install (works on any domain), and local dev gets none unless ONETRUST_ENV is set explicitly (ONETRUST_ENV=test pnpm dev shows the banner locally).
  • src/components/consent-tags.tsx renders the tags as plain blocking elements at the top of <body>, not via next/script — the DOM order is the legal contract: the OneTrust AutoBlocker must execute before the GTM snippet so it can gate non-consented cookies, and the GTM <noscript> iframe must sit immediately after the opening <body> tag. React hoists only the banner stylesheet <link> into <head> (where the www.databricks.com install puts it too); the scripts keep their <body> source order. GTM never loads without OneTrust.
  • Consent-change handling (SPA) — addresses review feedback: on www.databricks.com a consent change takes effect on the next full page load, which a SPA never has. A small inline script (ordered directly after the shared databricks onetrust.js) chains that script's OptanonWrapper — never replaces it, since its opt-out cookie deletion must keep running — snapshots OnetrustActiveGroups at SDK init, registers the documented OneTrust.OnConsentChanged callback exactly once, and reloads the page only when the active groups actually differ from the boot snapshot. The reload re-runs the whole tag stack against the new OptanonConsent cookie, reproducing multi-page semantics in both directions (late grant: tags start collecting; revoke: trackers unload, and the post-reload init re-runs cookie deletion). The snapshot guard makes reload loops structurally impossible and ignores SDK-internal re-fires (geo resolution, implied-consent recording, preference-center saves with no change).
  • src/components/your-privacy-choices-link.tsx renders the "Your Privacy Choices" link with the GPC icon in both the main and perspectives footers; it opens the OneTrust preference center (no-op where consent scripts are off).

Validation

  • Unit tests for the variant resolution (tests/onetrust.test.ts) and for the rendered consent tags (tests/consent-tags.test.ts): empty render without a consent variant, the load-bearing tag order (AutoBlocker → SDK stub → onetrust.js → consent-change handler → GTM loader), and the OptanonWrapper chaining/reload markers. Footer e2e expectations updated; the broken-link crawl allowlists the JS-handled #yourprivacychoices pseudo-anchor.
  • Verified in a real browser against a test-variant build: tag order in the served HTML, banner renders, and both consent-change directions: Reject All on the banner → page reloads itself, groups drop to strictly-necessary (,C0001,), no reload loop; then Allow All via the footer link's preference center → page reloads itself, full groups restored, GTM live post-reload.
  • Verified in a real browser on the Vercel preview deployment that the AutoBlocker does not block Vercel Analytics: /_vercel/insights/script.js is injected and fetched alongside the loaded OneTrust banner and GTM. (Analytics only renders when VERCEL=1, so this pairing can't be exercised locally.)
  • Verified a default local build emits zero consent/GTM markup.
  • Full suite: pnpm typecheck, pnpm exec vitest run, pnpm exec playwright test (182/183 — the one failure is a pre-existing image-load timeout on /templates/lakebase-off-platform that reproduces identically with this branch's changes stashed), pnpm build.

Verification steps before merge

  • OneTrust owner verifies the test-variant banner on the Vercel preview deployment (banner text, categories, Reject All behavior, preference center via the footer link, reload-on-consent-change)
  • After merge + production deploy: verify the production variant on developers.databricks.com, including GTM (not testable on preview since it is not a *.databricks.com domain)

This pull request and its description were written by Isaac.

@thisistonydang
thisistonydang force-pushed the thisistonydang/cookies branch from 2d83521 to 84fbde2 Compare July 14, 2026 21:23
@thisistonydang
thisistonydang marked this pull request as draft July 14, 2026 22:44
@thisistonydang
thisistonydang force-pushed the thisistonydang/cookies branch from 84fbde2 to 3f1adbb Compare July 18, 2026 23:53
@hamzaessaoui-databricks

hamzaessaoui-databricks commented Jul 18, 2026

Copy link
Copy Markdown

Native Next.js implementation: ConsentTags renders the OneTrust tags and
the GTM container as plain blocking tags at the top of <body> — the DOM
order is the legal contract (AutoBlocker before GTM so non-consented
cookies are gated) — gated by resolveOneTrustEnv: production variant on
production deploys, test variant on previews, nothing locally unless
ONETRUST_ENV is set. The CCPA "Your Privacy Choices" link opens the
OneTrust preference center from both the main and perspectives footers.

Co-authored-by: Isaac
@thisistonydang
thisistonydang force-pushed the thisistonydang/cookies branch from 3f1adbb to 8d5d6a4 Compare July 19, 2026 00:30
@thisistonydang
thisistonydang marked this pull request as ready for review July 20, 2026 00:51
The databricks onetrust.js defines OptanonWrapper and relies on the next
page load to apply consent changes; the SPA never has one. Chain the
wrapper (never replace it, its cookie deletion must keep running),
register OneTrust.OnConsentChanged once the SDK is up, and reload only
when OnetrustActiveGroups actually differs from the boot snapshot.

Co-authored-by: Isaac
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.

2 participants