refactor(analytics): replace PostHog with OpenPanel (#2480)

This commit is contained in:
RainbowBird
2026-09-09 08:26:18 +00:00
committed by GitHub
parent 5e78b64e35
commit 66f0d45028
68 changed files with 802 additions and 1252 deletions
+1 -1
View File
@@ -22,6 +22,7 @@
"@formkit/auto-animate": "catalog:",
"@moeru/eventa": "catalog:",
"@moeru/std": "catalog:",
"@openpanel/web": "catalog:",
"@proj-airi/font-chillroundm": "workspace:^",
"@proj-airi/font-cjkfonts-allseto": "workspace:^",
"@proj-airi/font-xiaolai": "workspace:^",
@@ -40,7 +41,6 @@
"nanoid": "catalog:",
"nprogress": "catalog:",
"pinia": "catalog:",
"posthog-js": "catalog:",
"uuid": "catalog:",
"vaul-vue": "catalog:",
"vue": "catalog:",
+3 -3
View File
@@ -24,10 +24,10 @@ import 'vue-sonner/style.css'
import './styles/main.css'
import 'uno.css'
if (isEnvTruthy(import.meta.env.VITE_ENABLE_POSTHOG)) {
if (isEnvTruthy(import.meta.env.VITE_ENABLE_ANALYTICS)) {
void loadAnalyticsAdapter(async () => {
const { createPosthogAdapter } = await import('./modules/analytics-adapters/posthog')
return createPosthogAdapter()
const { createOpenpanelAdapter } = await import('./modules/analytics-adapters/openpanel')
return createOpenpanelAdapter()
})
}
@@ -0,0 +1,34 @@
import type { AnalyticsAdapter } from '../analytics'
import { OpenPanel } from '@openpanel/web'
import { OPENPANEL_CONFIG } from '@proj-airi/stage-shared/analytics/openpanel'
/** Sends auth milestones to the same OpenPanel project as the stage apps. */
export function createOpenpanelAdapter(): AnalyticsAdapter {
const panel = new OpenPanel({
...OPENPANEL_CONFIG,
trackScreenViews: true,
filter(payload) {
// OAuth codes and other query values must not enter analytics.
if (payload.type === 'track' && payload.payload.properties) {
for (const key of ['__path', '__referrer']) {
const value = payload.payload.properties[key]
if (typeof value === 'string')
payload.payload.properties[key] = value.split(/[?#]/, 1)[0]
}
}
return true
},
})
panel.setGlobalProperties({ app_surface: 'auth' })
return {
capture(event, properties) {
// Auth navigation must proceed even when the analytics endpoint fails.
void panel.track(event, properties).catch(() => console.warn('[analytics] Product event delivery failed'))
},
identify(userId) {
panel.identify({ profileId: userId })
},
}
}
@@ -1,28 +0,0 @@
import type { AnalyticsAdapter } from '../analytics'
import posthog from 'posthog-js'
import {
DEFAULT_POSTHOG_CONFIG,
POSTHOG_PROJECT_KEY,
} from '@proj-airi/stage-shared/analytics/posthog'
/** Creates the auth analytics adapter and initializes its provider SDK. */
export function createPosthogAdapter(): AnalyticsAdapter {
posthog.init(POSTHOG_PROJECT_KEY, { ...DEFAULT_POSTHOG_CONFIG })
// The shared project distinguishes auth traffic through this super property.
posthog.register({ app_surface: 'auth' })
return {
capture(event, properties, options) {
posthog.capture(
event,
properties,
options?.beforeNavigation ? { send_instantly: true, transport: 'sendBeacon' } : undefined,
)
},
identify(userId) {
posthog.identify(userId)
},
}
}
@@ -23,7 +23,7 @@ describe('auth analytics', () => {
//
// The auth SPA emitted `signup_completed` before it knew the Better Auth
// user id, while the server emitted the same canonical event with that id.
// PostHog therefore counted one email signup as two unrelated persons.
// The analytics provider therefore counted one email signup as two unrelated persons.
//
// The anonymous UI milestone must use its own name. The identified server
// event remains the only canonical `signup_completed` business fact.
+1 -1
View File
@@ -63,7 +63,7 @@ export default defineConfig({
chunkFileNames: (chunkInfo) => {
const containsAnalyticsModule = chunkInfo.moduleIds.some((moduleId) => {
const normalizedModuleId = moduleId.replaceAll('\\', '/').toLowerCase()
return normalizedModuleId.includes('analytics') || normalizedModuleId.includes('posthog')
return normalizedModuleId.includes('analytics') || normalizedModuleId.includes('openpanel')
})
// Keep analytics as the source-domain name, but explicitly map its