Skip to content

Commit b6f76a5

Browse files
gakonstampcode-com
andauthored
feat: add PostHog tracking for docs feedback (#101)
- Add posthog-node for server-side event capture - Create combined feedback adapter (Slack + PostHog) - Track docs_feedback_submitted, docs_feedback_helpful, docs_feedback_not_helpful events - Include page URL, category, and message in event properties - Enable session recording with password masking Amp-Thread-ID: https://ampcode.com/threads/T-019c4d62-38c2-7005-bbd3-3ad64e139c61 Co-authored-by: Amp <amp@ampcode.com>
1 parent 3833731 commit b6f76a5

6 files changed

Lines changed: 109 additions & 2 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"monaco-editor": "^0.55.1",
2626
"ox": "^0.11.3",
2727
"posthog-js": "^1.333.0",
28+
"posthog-node": "^5.24.15",
2829
"prool": "^0.2.2",
2930
"react": "^19.2.3",
3031
"react-dom": "^19.2.3",

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/PostHogSetup.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ function PostHogInitializer() {
1616
defaults: '2025-11-30',
1717
capture_exceptions: true,
1818
debug: import.meta.env.MODE === 'development',
19+
session_recording: {
20+
maskAllInputs: false,
21+
maskInputOptions: {
22+
password: true,
23+
},
24+
},
1925
})
2026

2127
posthog.register({ site: 'docs' })

src/lib/feedback-adapter.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { PostHog } from 'posthog-node'
2+
import { Feedback } from 'vocs/config'
3+
import { POSTHOG_EVENTS, POSTHOG_PROPERTIES } from './posthog'
4+
5+
type FeedbackData = {
6+
helpful: boolean
7+
category?: string | undefined
8+
message?: string | undefined
9+
pageUrl: string
10+
timestamp: string
11+
}
12+
13+
export function createFeedbackAdapter() {
14+
const slackAdapter = Feedback.slack()
15+
16+
const posthogKey = process.env.VITE_POSTHOG_KEY
17+
const posthogHost = process.env.VITE_POSTHOG_HOST || 'https://us.i.posthog.com'
18+
19+
const posthog = posthogKey ? new PostHog(posthogKey, { host: posthogHost }) : null
20+
21+
return Feedback.from({
22+
type: 'slack+posthog',
23+
async submit(data: FeedbackData) {
24+
const slackPromise = slackAdapter.submit(data)
25+
26+
const posthogPromise = (async () => {
27+
if (!posthog) return
28+
29+
let pagePath: string | undefined
30+
try {
31+
pagePath = new URL(data.pageUrl).pathname
32+
} catch {
33+
pagePath = undefined
34+
}
35+
36+
const distinctId = `docs_feedback_${Date.now()}_${Math.random().toString(36).slice(2)}`
37+
const ts = data.timestamp ? new Date(data.timestamp) : undefined
38+
39+
const commonProperties = {
40+
[POSTHOG_PROPERTIES.FEEDBACK_HELPFUL]: data.helpful,
41+
[POSTHOG_PROPERTIES.FEEDBACK_CATEGORY]: data.category,
42+
[POSTHOG_PROPERTIES.FEEDBACK_MESSAGE]: data.message,
43+
[POSTHOG_PROPERTIES.FEEDBACK_PAGE_URL]: data.pageUrl,
44+
[POSTHOG_PROPERTIES.PAGE_PATH]: pagePath,
45+
[POSTHOG_PROPERTIES.SITE]: 'docs',
46+
}
47+
48+
posthog.capture({
49+
distinctId,
50+
event: POSTHOG_EVENTS.FEEDBACK_SUBMITTED,
51+
properties: commonProperties,
52+
timestamp: ts,
53+
})
54+
55+
posthog.capture({
56+
distinctId,
57+
event: data.helpful
58+
? POSTHOG_EVENTS.FEEDBACK_HELPFUL
59+
: POSTHOG_EVENTS.FEEDBACK_NOT_HELPFUL,
60+
properties: commonProperties,
61+
timestamp: ts,
62+
})
63+
64+
await posthog.flush()
65+
})().catch(() => {})
66+
67+
await Promise.allSettled([slackPromise, posthogPromise])
68+
},
69+
})
70+
}

src/lib/posthog.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export const POSTHOG_EVENTS = {
3535
// Code interactions
3636
CODE_EXAMPLE_VIEW: 'docs_code_example_view',
3737
CODE_EXAMPLE_COPY: 'docs_code_example_copy',
38+
39+
// Feedback
40+
FEEDBACK_SUBMITTED: 'docs_feedback_submitted',
41+
FEEDBACK_HELPFUL: 'docs_feedback_helpful',
42+
FEEDBACK_NOT_HELPFUL: 'docs_feedback_not_helpful',
3843
} as const
3944

4045
/**
@@ -68,6 +73,12 @@ export const POSTHOG_PROPERTIES = {
6873
SEARCH_QUERY: 'search_query',
6974
SEARCH_RESULT_TITLE: 'search_result_title',
7075
SEARCH_RESULT_URL: 'search_result_url',
76+
77+
// Feedback properties
78+
FEEDBACK_HELPFUL: 'feedback_helpful',
79+
FEEDBACK_CATEGORY: 'feedback_category',
80+
FEEDBACK_MESSAGE: 'feedback_message',
81+
FEEDBACK_PAGE_URL: 'feedback_page_url',
7182
} as const
7283

7384
/**

vocs.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Changelog, defineConfig, Feedback, McpSource } from 'vocs/config'
1+
import { Changelog, defineConfig, McpSource } from 'vocs/config'
2+
import { createFeedbackAdapter } from './src/lib/feedback-adapter'
23

34
const baseUrl = (() => {
45
if (URL.canParse(process.env.VITE_BASE_URL)) return process.env.VITE_BASE_URL
@@ -22,7 +23,7 @@ export default defineConfig({
2223
title: 'Tempo',
2324
titleTemplate: '%s ⋅ Tempo',
2425
description: 'Documentation for the Tempo network and protocol specifications',
25-
feedback: Feedback.slack(),
26+
feedback: createFeedbackAdapter(),
2627
mcp: {
2728
enabled: true,
2829
sources: [

0 commit comments

Comments
 (0)