feat(analytics): extend analytics service with posthog-node SDK#3637
feat(analytics): extend analytics service with posthog-node SDK#3637PierreBrisorgueil merged 1 commit intomasterfrom
Conversation
Extends the existing lib/services/analytics.js to wrap posthog-node :
- New config block analytics.posthog (enabled, apiKey, host, appTag,
flushAt, flushInterval) gated by POSTHOG_ENABLED env var. Default off
so existing downstream projects without env stay untouched.
- New service exports : identify(distinctId, properties),
capture({ distinctId, event, properties }), shutdown(). Auto-injects
app=appTag + env=NODE_ENV on every capture (custom properties win).
- Auth controller wires identify + capture(user_signed_up) on signup
and capture(user_signed_in) on signin (generic events — downstream
projects add their own business events on top).
- SIGTERM/SIGINT shutdown hook flushes pending events.
- 3 new test files covering identify, capture, lifecycle resilience.
Project-specific tagging via POSTHOG_APP_TAG=trawl|comes|... env var
in each downstream's deployment manifest. PostHog Cloud target
(https://eu.i.posthog.com default).
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3637 +/- ##
==========================================
+ Coverage 88.89% 88.92% +0.03%
==========================================
Files 135 135
Lines 4645 4661 +16
Branches 1432 1441 +9
==========================================
+ Hits 4129 4145 +16
Misses 403 403
Partials 113 113
Flags with carried forward coverage won't be shown. Click here to find out more. Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Extends the existing analytics service to optionally wrap the posthog-node SDK (opt-in via config/env), adds a higher-level capture() helper with automatic app/env context injection, and wires basic auth lifecycle events (user_signed_up, user_signed_in) to analytics. This aligns with the repo’s existing PostHog-based feature flag and analytics plumbing while keeping analytics fail-safe (no-op / fire-and-forget).
Changes:
- Add
posthog.enabledgating,capture()helper, and configurable flush options tolib/services/analytics.js. - Emit
identify+captureevents from auth signup/signin flows. - Expand unit test coverage for
capture()behavior + update existing analytics tests/config defaults for the newenabledflag and EU default host.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| modules/auth/controllers/auth.controller.js | Adds capture calls for user_signed_up / user_signed_in (fire-and-forget). |
| lib/services/analytics.js | Adds enabled gating, capture() with app/env injection, flush options, and resets appTag on shutdown. |
| config/defaults/development.config.js | Introduces posthog.enabled default-off plus flush defaults and appTag comment. |
| .env.example | Updates PostHog docs/values for EU and adds DEVKIT_NODE_posthog_enabled + appTag. |
| lib/services/tests/analytics.service.unit.tests.js | Updates mocks to include enabled and changes default host expectation to EU. |
| lib/services/tests/analytics.service.resilience.unit.tests.js | Updates config mock to include enabled. |
| lib/services/tests/analytics.forRequest.unit.tests.js | Updates config mock to include enabled. |
| lib/services/tests/analytics.identify.unit.tests.js | Extends auth controller tests to assert capture() calls; updates analytics mock shape. |
| lib/services/tests/analytics.capture.unit.tests.js | New unit tests covering capture() behavior, injection, and shutdown idempotency. |
Comments suppressed due to low confidence (1)
lib/services/tests/analytics.identify.unit.tests.js:618
- In this
beforeEach,jest.unstable_mockModule('../analytics.js', ...)is declared three times with identical factories; only the last registration will take effect. This redundancy makes the test setup harder to reason about and risks accidental divergence—consider keeping a single mockModule registration here.
jest.unstable_mockModule('../analytics.js', () => ({
default: {
identify: jest.fn(),
groupIdentify: mockGroupIdentify,
capture: jest.fn(),
track: jest.fn(),
init: jest.fn().mockResolvedValue(undefined),
shutdown: jest.fn(),
},
}));
jest.unstable_mockModule('../analytics.js', () => ({
default: {
identify: jest.fn(),
groupIdentify: mockGroupIdentify,
capture: jest.fn(),
track: jest.fn(),
init: jest.fn().mockResolvedValue(undefined),
shutdown: jest.fn(),
},
}));
jest.unstable_mockModule('../analytics.js', () => ({
default: {
identify: jest.fn(),
groupIdentify: mockGroupIdentify,
capture: jest.fn(),
track: jest.fn(),
init: jest.fn().mockResolvedValue(undefined),
shutdown: jest.fn(),
},
| const { enabled, apiKey, host, flushAt, flushInterval, appTag } = config.posthog ?? {}; | ||
| if (!enabled || !apiKey) return; | ||
| const { PostHog } = await import('posthog-node'); | ||
| client = new PostHog(apiKey, { host: host || 'https://us.i.posthog.com' }); | ||
| const options = { host: host || 'https://eu.i.posthog.com' }; | ||
| if (flushAt != null) options.flushAt = flushAt; | ||
| if (flushInterval != null) options.flushInterval = flushInterval; | ||
| client = new PostHog(apiKey, options); |
| // apiKey: process.env.DEVKIT_NODE_posthog_apiKey ?? '', | ||
| // host: process.env.DEVKIT_NODE_posthog_host ?? 'https://us.i.posthog.com', | ||
| // host: process.env.DEVKIT_NODE_posthog_host ?? 'https://eu.i.posthog.com', | ||
| // appTag: process.env.DEVKIT_NODE_posthog_appTag ?? '', // e.g. 'trawl', 'comes' — auto-injected on every capture |
| # PostHog Analytics | ||
| # Get your keys from https://us.posthog.com/settings/project-api-key | ||
| # Get your keys from https://eu.posthog.com/settings/project-api-key | ||
| DEVKIT_NODE_posthog_enabled=true | ||
| DEVKIT_NODE_posthog_apiKey=phc_xxx | ||
| DEVKIT_NODE_posthog_host=https://us.i.posthog.com | ||
| DEVKIT_NODE_posthog_host=https://eu.i.posthog.com | ||
| DEVKIT_NODE_posthog_appTag=myproject |
Summary
lib/services/analytics.jsto wrapposthog-node(no new file — generic posthog wrapper merged into the existing analytics service)identify+capture+shutdownexports, gated byPOSTHOG_ENABLEDenv (default off — opt-in per downstream project)app=appTag(fromPOSTHOG_APP_TAGenv) +env=NODE_ENVon every captureuser_signed_up,user_signed_in) in the auth controllerWhy
PostHog self-host on lily was abandoned (cf infra plan
2026-05-08-posthog-self-host.md— PostHog is conceived for its Helm chart, manifests-flat path diverged). Pivoted to PostHog Cloud EU free tier (1M events/mo, well above projected Trawl volume).This Devkit PR ships the generic SDK wrapper. Downstream projects (Trawl first, then Comes/Pierreb/Montaine) will get it via
/update-project, then add their business-specific events (e.g.scrap_run_completed,subscription_changed) on top in their own PRs.Test plan
Downstream propagation note
After this merges, run
/update-projectselecting Trawl first to propagate. Each downstream then adds :POSTHOG_APP_TAG=<project-name>in deployment envPOSTHOG_API_KEYfrom project's PostHog Cloud project key (K8s secret)