Skip to content

feat(docs): DR-7849 DR-7850 PostHog tracking + UTM injection#7697

Open
ArthurGamby wants to merge 3 commits intomainfrom
dr-7850-remark-console-utm
Open

feat(docs): DR-7849 DR-7850 PostHog tracking + UTM injection#7697
ArthurGamby wants to merge 3 commits intomainfrom
dr-7850-remark-console-utm

Conversation

@ArthurGamby
Copy link
Contributor

@ArthurGamby ArthurGamby commented Mar 24, 2026

Summary

  • Add PostHog content area super properties (content_area, is_ppg_or_compute, content_subtype) registered on every route change
  • Track high-intent user actions: copy prompt, copy markdown, open in external tool (GitHub/ChatGPT/Claude/T3)
  • Track console.prisma.io link clicks with UTM presence detection
  • Add remark plugin that auto-injects utm_source, utm_medium, utm_content on all console.prisma.io links at MDX build time
  • v6 docs use utm_source=docs-v6, v7 uses utm_source=docs
  • Links with existing utm_source are left untouched

Linear

Preview

Preview

Test plan

  1. Open the preview site.
  2. Go to /docs/postgres/npx-create-db
  3. Scroll down to Step 3
  4. Hover over Prisma Data Platform account
  5. Verify the link includes:
    https://console.prisma.io/?utm_source=docs&utm_medium=content&utm_content=postgres

✅ If the UTM params are present, the remark plugin is working correctly.

Screenshot 2026-03-25 at 17 07 44

- Add content area super properties (content_area, is_ppg_or_compute, content_subtype)
- Track high-intent actions: copy prompt, copy markdown, open external tool
- Track console link clicks with UTM presence
- Add remark plugin for automatic UTM injection on console.prisma.io links
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

Walkthrough

This pull request introduces comprehensive analytics and tracking instrumentation to the documentation site. Changes include adding PostHog event capture for user interactions (prompt copying, markdown copying, external tool access), automatic UTM parameter injection for console.prisma.io links via a new remark plugin, content area classification utilities, and a new tracking provider component for monitoring link clicks and page navigation.

Changes

Cohort / File(s) Summary
Dependencies & Configuration
apps/docs/package.json, apps/docs/source.config.ts
Added unist-util-visit, unified, and @types/mdast dependencies. Integrated remarkConsoleUtm plugin into the MDX remark plugin pipeline; reformatted imports to single-line destructured format.
Tracking Utilities
apps/docs/src/lib/tracking.ts, apps/docs/src/lib/remark-console-utm.ts
Created tracking.ts to classify documentation routes into content areas (ppg, orm, accelerate, console, guides, ai, other) and detect subtypes (quickstart, getting-started). Created remark-console-utm.ts plugin to parse and rewrite console.prisma.io URLs with UTM parameters based on documentation version and content section.
Component Integration
apps/docs/src/components/page-actions.tsx, apps/docs/src/components/provider.tsx, apps/docs/src/components/tracking-provider.tsx
Integrated PostHog event tracking into page-actions component for prompt/markdown copy and external tool access actions. Added TrackingProvider wrapper to the root provider and converted quote styles to double quotes. Created new TrackingProvider component that derives pathname, registers PostHog content properties, and captures console link click events with UTM detection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main changes: PostHog tracking integration and UTM injection for console links, with direct references to the Linear issues being addressed.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@vercel
Copy link

vercel bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Mar 27, 2026 3:11pm
docs Ready Ready Preview, Comment Mar 27, 2026 3:11pm
eclipse Ready Ready Preview, Comment Mar 27, 2026 3:11pm
site Ready Ready Preview, Comment Mar 27, 2026 3:11pm

Request Review

@argos-ci
Copy link

argos-ci bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 1 changed Mar 27, 2026, 3:11 PM

@ArthurGamby ArthurGamby marked this pull request as ready for review March 25, 2026 15:58
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 (2)
apps/docs/src/lib/tracking.ts (1)

28-31: Consider more precise matching for subtypes.

Using includes() will match the substring anywhere in the path. For example, /docs/orm/some-quickstart-guide would match even if "quickstart" is just part of a slug. This might be intentional for broad matching, but if you want exact segment matching:

♻️ Optional: Match path segments more precisely
 export function getContentSubtype(pathname: string): string | null {
-  if (pathname.includes("/quickstart")) return "quickstart";
-  if (pathname.includes("/getting-started")) return "getting-started";
+  const segments = pathname.split("/");
+  if (segments.includes("quickstart")) return "quickstart";
+  if (segments.includes("getting-started")) return "getting-started";
   return null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs/src/lib/tracking.ts` around lines 28 - 31, The getContentSubtype
function currently uses pathname.includes(...) which can match substrings inside
slugs; change it to match path segments more precisely by checking the pathname
segments or using anchored regexes (e.g. match "/quickstart" or "/quickstart/"
and similarly for "getting-started") so only a full segment triggers; update
getContentSubtype to split pathname on "/" and check segment equality or use
regex like /(^|\/)quickstart(\/|$)/ and /(^|\/)getting-started(\/|$)/ to return
the correct subtype.
apps/docs/src/components/tracking-provider.tsx (1)

19-36: Consider stable handler reference with useCallback for minor optimization.

The click handler is recreated and the event listener is removed/re-added on every pathname change. While functionally correct (it ensures pathname is fresh in the closure), this is slightly inefficient.

That said, route changes are infrequent enough that this is unlikely to cause any performance issues in practice. The current implementation is clear and correct.

♻️ Optional: Use ref to avoid re-registering listener
+"use client";
+import { useEffect, useRef } from "react";
+import { usePathname } from "next/navigation";
+import posthog from "posthog-js";
+import { getContentArea, getContentSubtype, isPpgOrCompute } from "@/lib/tracking";
+
+export function TrackingProvider() {
+  const pathname = usePathname();
+  const pathnameRef = useRef(pathname);
+  pathnameRef.current = pathname;
+
   // ... first useEffect unchanged ...
 
   useEffect(() => {
     function handleClick(e: MouseEvent) {
       const anchor = (e.target as HTMLElement).closest<HTMLAnchorElement>(
         'a[href*="console.prisma.io"]',
       );
       if (!anchor) return;
 
       const url = anchor.href;
       posthog.capture("docs:console_link_click", {
-        page_path: pathname,
+        page_path: pathnameRef.current,
         destination_url: url,
         has_utm: url.includes("utm_source"),
       });
     }
 
     document.addEventListener("click", handleClick);
     return () => document.removeEventListener("click", handleClick);
-  }, [pathname]);
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs/src/components/tracking-provider.tsx` around lines 19 - 36, The
click handler handleClick is recreated on every pathname change causing the
listener to be removed/re-added; make the handler reference stable by creating a
ref (e.g., pathnameRef) that you update with the latest pathname and then wrap
handleClick in useCallback with an empty dependency array so the same function
instance is registered once in the useEffect; inside handleClick read
pathnameRef.current and call posthog.capture with destination_url and has_utm as
before, and keep the document.addEventListener/removeEventListener logic using
this stable handleClick to avoid re-registering on route changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/docs/src/lib/tracking.ts`:
- Around line 14-26: The function name isPpgOrCompute is misleading because
PPG_AREAS only contains "ppg" and "compute" is not a ContentArea; either rename
the function to isPpg (and update its callers and the PostHog property
is_ppg_or_compute in tracking-provider.tsx) if "compute" is intentionally
removed, or restore "compute" into PPG_AREAS (and ContentArea) if it should be
tracked separately; locate symbols isPpgOrCompute, PPG_AREAS, PREFIX_MAP and
update tracking-provider.tsx usage and the PostHog property name accordingly to
keep names consistent.

---

Nitpick comments:
In `@apps/docs/src/components/tracking-provider.tsx`:
- Around line 19-36: The click handler handleClick is recreated on every
pathname change causing the listener to be removed/re-added; make the handler
reference stable by creating a ref (e.g., pathnameRef) that you update with the
latest pathname and then wrap handleClick in useCallback with an empty
dependency array so the same function instance is registered once in the
useEffect; inside handleClick read pathnameRef.current and call posthog.capture
with destination_url and has_utm as before, and keep the
document.addEventListener/removeEventListener logic using this stable
handleClick to avoid re-registering on route changes.

In `@apps/docs/src/lib/tracking.ts`:
- Around line 28-31: The getContentSubtype function currently uses
pathname.includes(...) which can match substrings inside slugs; change it to
match path segments more precisely by checking the pathname segments or using
anchored regexes (e.g. match "/quickstart" or "/quickstart/" and similarly for
"getting-started") so only a full segment triggers; update getContentSubtype to
split pathname on "/" and check segment equality or use regex like
/(^|\/)quickstart(\/|$)/ and /(^|\/)getting-started(\/|$)/ to return the correct
subtype.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f9ec0067-9d17-4d60-b68a-ae522842ca3f

📥 Commits

Reviewing files that changed from the base of the PR and between 48398aa and dcac8b2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/docs/package.json
  • apps/docs/source.config.ts
  • apps/docs/src/components/page-actions.tsx
  • apps/docs/src/components/provider.tsx
  • apps/docs/src/components/tracking-provider.tsx
  • apps/docs/src/lib/remark-console-utm.ts
  • apps/docs/src/lib/tracking.ts

Comment on lines +14 to +26
const PPG_AREAS: Set<ContentArea> = new Set(["ppg"]);

export function getContentArea(pathname: string): ContentArea {
const stripped = pathname.replace(/^\/docs\/v\d+/, "");
for (const [prefix, area] of PREFIX_MAP) {
if (stripped.startsWith(prefix)) return area;
}
return "other";
}

export function isPpgOrCompute(area: ContentArea): boolean {
return PPG_AREAS.has(area);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Function name isPpgOrCompute may be misleading.

The function name suggests it checks for both "ppg" and "compute" areas, but PPG_AREAS only contains "ppg", and "compute" isn't in the ContentArea type.

If "compute" was intentionally removed or consolidated into "ppg", consider renaming to isPpg() for clarity. If "compute" should be tracked separately, it may need to be added.

♻️ Suggested rename if compute is not needed
-const PPG_AREAS: Set<ContentArea> = new Set(["ppg"]);
+const PPG_AREAS: Set<ContentArea> = new Set(["ppg"]);

-export function isPpgOrCompute(area: ContentArea): boolean {
+export function isPpg(area: ContentArea): boolean {
   return PPG_AREAS.has(area);
 }

Note: You'd also need to update the call site in tracking-provider.tsx and the PostHog property name is_ppg_or_compute.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs/src/lib/tracking.ts` around lines 14 - 26, The function name
isPpgOrCompute is misleading because PPG_AREAS only contains "ppg" and "compute"
is not a ContentArea; either rename the function to isPpg (and update its
callers and the PostHog property is_ppg_or_compute in tracking-provider.tsx) if
"compute" is intentionally removed, or restore "compute" into PPG_AREAS (and
ContentArea) if it should be tracked separately; locate symbols isPpgOrCompute,
PPG_AREAS, PREFIX_MAP and update tracking-provider.tsx usage and the PostHog
property name accordingly to keep names consistent.

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