Skip to content

feat(studio): posthog cookieless tracking with consent gating#3028

Merged
thisisnithin merged 3 commits into
mainfrom
nithin/eng-9790-posthog-cookieless-tracking
Jun 29, 2026
Merged

feat(studio): posthog cookieless tracking with consent gating#3028
thisisnithin merged 3 commits into
mainfrom
nithin/eng-9790-posthog-cookieless-tracking

Conversation

@thisisnithin

@thisisnithin thisisnithin commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Integrated Osano analytics consent with PostHog tracking, enabling data capture only after consent is granted.
    • Switches PostHog between opt-in and opt-out modes based on current consent and refreshes feature flags when opting in.
  • Bug Fixes

    • Prevents user identification and tracking setup until analytics consent is approved.
    • Applies consent decisions as soon as Osano is available and keeps behavior in sync when consent changes.
  • Chores

    • Updated the PostHog JavaScript library dependency.

Summary

Adds PostHog cookieless tracking with consent gating to the studio (ENG-9790).

Previously PostHog initialized and captured everyone fully — cookies and PII via identify() — and ignored Osano consent entirely (Osano was only wired to Google Tag Manager). This change brings PostHog in line with the existing consent model:

  • Add cookieless_mode: 'on_reject' so users who reject analytics or have no prior consent are tracked anonymously (no cookies, privacy-preserving hash) instead of fully.
  • Wire Osano consent → PostHog opt_in_capturing() / opt_out_capturing() (vanilla window.Osano, mirroring the existing GTM consent handler).
  • Gate identify()/group association behind posthog.has_opted_in_capturing() so no PII is attached for non-consenting users.
  • Bump posthog-js ^1.240.6^1.395.0 (required for cookieless_mode).

Resulting behavior:

Consent state Tracking Identity (PII)
No prior consent / reject Anonymous (cookieless) No
Accept Full (cookies) Yes

Note: Osano only loads in production, so in dev PostHog stays cookieless/anonymous.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • If applicable, I have provided instructions for reviewers for manually validating my changes.
  • Documentation has been updated.
  • I have read the Contributors Guide.

Closes ENG-9790

ENG-9790: add cookieless_mode 'on_reject' so rejected/default opt-out
users are tracked anonymously, wire Osano consent to PostHog opt-in/out
(previously PostHog ignored consent entirely), and gate identify()/PII
behind opt-in. Bump posthog-js to ^1.395.0 for cookieless_mode support.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d2e19e3-c6da-4a4b-a1d5-966ad3a28597

📥 Commits

Reviewing files that changed from the base of the PR and between e2f5fac and f2e578d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • studio/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • studio/package.json

Walkthrough

Adds Osano consent handling to Studio, updates PostHog initialization and runtime opt-in/opt-out behavior, and gates identify() calls behind PostHog opt-in. The PostHog client dependency is also updated.

Changes

Osano Consent + PostHog Integration

Layer / File(s) Summary
Osano consent hook and typings
studio/src/hooks/use-analytics-consent.ts
Defines Osano consent types, extends Window for the consent manager API, and adds consent-checking helpers plus a React hook that tracks consent state.
PostHog consent wiring in app entry
studio/src/pages/_app.tsx, studio/package.json
Updates PostHog init to use cookieless_mode: 'on_reject', adds consent-driven opt-in/opt-out handling for Osano events, and bumps posthog-js to ^1.395.0.
Identify payload and opt-in gate
studio/src/lib/track.ts
Introduces TrackingIdentity, updates setupPosthog to use it, and gates PostHog identity setup on posthog.has_opted_in_capturing() while keeping Reo setup.
Session tracking reacts to consent
studio/src/components/app-provider.tsx
Feeds analytics consent into the session-processing effect so it re-runs when consent changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • wundergraph/cosmo#2700: Both PRs modify studio/src/lib/track.ts’s identify PostHog logic; this PR adds consent gating and identity reshaping, while that PR changes distinct-id/email/grouping behavior.
  • wundergraph/cosmo#2768: Both PRs touch the shared PostHog tracking module in studio/src/lib/track.ts; this PR changes the identify API and opt-in gating, while that PR adds typed onboarding event capture.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: PostHog cookieless tracking with consent gating in studio.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
studio/src/pages/_app.tsx (1)

30-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the new Osano types and callbacks with the TS guidelines.

Use an interface for the consent object shape and annotate the local callback return types.

Proposed cleanup
-type OsanoConsent = { ANALYTICS?: string; MARKETING?: string };
+interface OsanoConsent {
+  ANALYTICS?: string;
+  MARKETING?: string;
+}
...
-    const applyConsent = () => {
+    const applyConsent = (): void => {
...
-    const onOsanoReady = () => {
+    const onOsanoReady = (): void => {

As per coding guidelines, TypeScript object shapes should prefer interfaces, and function return types should be explicit.

Also applies to: 83-95

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio/src/pages/_app.tsx` around lines 30 - 37, The Osano typings in
_app.tsx use a type alias and leave callback/return annotations implicit, which
does not match the TS guidelines. Replace the OsanoConsent type alias with an
interface, and update the Osano.cm API declarations and the local
consent-handling callback(s) to use explicit return types and parameter types.
Use the existing Osano, getConsent, and addEventListener symbols to locate the
declarations and keep the shape consistent across the related code.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@studio/src/lib/track.ts`:
- Around line 113-115: The consent check in track.ts only covers PostHog, but
setupReo(email) still runs in production and can send PII even when opt-in is
rejected or default. Update the tracking flow around the consent gate used in
the track function so Reo is only initialized when
posthog.has_opted_in_capturing() is true, or else adjust the nearby comment to
explicitly apply only to PostHog. Use the existing setupReo and
posthog.has_opted_in_capturing symbols to keep the behavior aligned.

In `@studio/src/pages/_app.tsx`:
- Around line 85-87: The consent-granted branch in _app.tsx only calls
posthog.opt_in_capturing() and reloadFeatureFlags(), so users who identified
before Osano opt-in can stay anonymous. Update the consent handling path around
posthog.opt_in_capturing() to immediately replay the current user/org identity
after opt-in, or have studio/src/lib/track.ts persist and restore a pending
identify payload so identify() is re-run once consent is accepted.

---

Nitpick comments:
In `@studio/src/pages/_app.tsx`:
- Around line 30-37: The Osano typings in _app.tsx use a type alias and leave
callback/return annotations implicit, which does not match the TS guidelines.
Replace the OsanoConsent type alias with an interface, and update the Osano.cm
API declarations and the local consent-handling callback(s) to use explicit
return types and parameter types. Use the existing Osano, getConsent, and
addEventListener symbols to locate the declarations and keep the shape
consistent across the related code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 17edcf08-ebd8-4695-b441-72d598e3fe86

📥 Commits

Reviewing files that changed from the base of the PR and between 6e88b50 and 74f62ce.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • studio/package.json
  • studio/src/lib/track.ts
  • studio/src/pages/_app.tsx

Comment thread studio/src/lib/track.ts
Comment thread studio/src/pages/_app.tsx Outdated

@wilsonrivera wilsonrivera left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Add useAnalyticsConsent hook (reactive Osano consent state) and re-run
identify() from app-provider when consent is granted, replacing the
module-level identity cache. Centralize the analytics-consent check in
hasAnalyticsConsent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
studio/src/pages/_app.tsx (1)

71-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate the new consent helpers with explicit return types.

applyConsent and onOsanoReady are new TypeScript functions, so they should declare : void explicitly to match the repo rule. As per coding guidelines, "Use explicit type annotations for function parameters and return types in TypeScript".

Suggested fix
-    const applyConsent = () => {
+    const applyConsent = (): void => {
       if (hasAnalyticsConsent(window.Osano?.cm?.getConsent?.())) {
         posthog.opt_in_capturing();
         posthog.reloadFeatureFlags();
       } else {
         posthog.opt_out_capturing();
       }
     };
 
-    const onOsanoReady = () => {
+    const onOsanoReady = (): void => {
       applyConsent();
       window.Osano?.cm?.addEventListener('osano-cm-consent-changed', applyConsent);
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio/src/pages/_app.tsx` around lines 71 - 83, Add explicit void return
types to the new TypeScript helpers in _app.tsx: annotate both applyConsent and
onOsanoReady as returning void. Update the function declarations directly where
these consent-handling helpers are defined so they follow the repo rule for
explicit parameter and return type annotations.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@studio/src/hooks/use-analytics-consent.ts`:
- Around line 19-21: The consent gate in hasAnalyticsConsent is too broad
because it treats MARKETING-only acceptance as analytics consent. Update
hasAnalyticsConsent so it returns true only when ANALYTICS is ACCEPT, and remove
the MARKETING fallback; keep this predicate as the sole check used by _app.tsx
and app-provider.tsx for PostHog opt-in and identify replay.

---

Nitpick comments:
In `@studio/src/pages/_app.tsx`:
- Around line 71-83: Add explicit void return types to the new TypeScript
helpers in _app.tsx: annotate both applyConsent and onOsanoReady as returning
void. Update the function declarations directly where these consent-handling
helpers are defined so they follow the repo rule for explicit parameter and
return type annotations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d7cb5840-aa1b-457c-8afc-7ec4864aa5f6

📥 Commits

Reviewing files that changed from the base of the PR and between 74f62ce and e2f5fac.

📒 Files selected for processing (4)
  • studio/src/components/app-provider.tsx
  • studio/src/hooks/use-analytics-consent.ts
  • studio/src/lib/track.ts
  • studio/src/pages/_app.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • studio/src/lib/track.ts

Comment thread studio/src/hooks/use-analytics-consent.ts
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.64%. Comparing base (f144989) to head (f2e578d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
studio/src/pages/_app.tsx 0.00% 23 Missing ⚠️
studio/src/hooks/use-analytics-consent.ts 0.00% 20 Missing and 1 partial ⚠️
studio/src/lib/track.ts 0.00% 5 Missing ⚠️
studio/src/components/app-provider.tsx 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3028      +/-   ##
==========================================
+ Coverage   46.53%   47.64%   +1.11%     
==========================================
  Files        1120     1141      +21     
  Lines      154966   159485    +4519     
  Branches    10060    11060    +1000     
==========================================
+ Hits        72107    75987    +3880     
- Misses      81025    81641     +616     
- Partials     1834     1857      +23     
Files with missing lines Coverage Δ
studio/src/components/app-provider.tsx 0.00% <0.00%> (ø)
studio/src/lib/track.ts 0.00% <0.00%> (ø)
studio/src/hooks/use-analytics-consent.ts 0.00% <0.00%> (ø)
studio/src/pages/_app.tsx 0.00% <0.00%> (ø)

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-bb9fc6c2199ce1dc8ce56e3493d4ab7161d6b1d4-nonroot

@thisisnithin thisisnithin merged commit b7d9865 into main Jun 29, 2026
56 checks passed
@thisisnithin thisisnithin deleted the nithin/eng-9790-posthog-cookieless-tracking branch June 29, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants