Added mixpanel to app to monitor what is acutally being used by how much#2078
Added mixpanel to app to monitor what is acutally being used by how much#2078GelatoGenesis merged 3 commits intomainfrom
Conversation
Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
📝 WalkthroughWalkthroughAdds Mixpanel client analytics: a frontend Mixpanel service, a MixpanelSetup React component wired into Providers that respects cookie consent and route changes, new unit tests, an env schema entry and CI exposure for NEXT_PUBLIC_MIXPANEL_TOKEN, and the mixpanel-browser dependency. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Router as Next.js Router
participant CookieConsent as CookieConsentContext
participant MixpanelSetup
participant Analytics as Mixpanel Service
participant Mixpanel
User->>Router: navigate (pathname change)
Router->>MixpanelSetup: pathname update
MixpanelSetup->>CookieConsent: read performanceConsent
alt performanceConsent = true
MixpanelSetup->>Analytics: initAnalytics() [if not initialized]
Analytics->>Mixpanel: initialize with token & options
Mixpanel-->>Analytics: ready
MixpanelSetup->>Analytics: trackPageView(path, {has_connected_profile})
Analytics->>Mixpanel: track("Page Viewed", properties)
alt connectedProfile exists
MixpanelSetup->>Analytics: identify(profileId, traits)
Analytics->>Mixpanel: set distinct_id & update people
end
else performanceConsent = false
MixpanelSetup->>Analytics: disableAnalytics() / clearIdentity()
Analytics->>Mixpanel: clear state
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/cookies/CookieConsentContext.tsx (1)
78-103:⚠️ Potential issue | 🟠 MajorRevoking performance consent still leaves the GA runtime active.
Line 83 only prevents duplicate injection; it does not disable the already-loaded analytics runtime when consent later becomes
false. After one accept, the current tab can keep sending performance data until reload, which breaks consent withdrawal semantics. Please add a symmetric disable/teardown path whenperformanceConsentis revoked.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/cookies/CookieConsentContext.tsx` around lines 78 - 103, Add a teardown path that reverses what loadPerformanceCookies does when performanceConsent becomes false: when performanceConsent transitions to false (watch the performanceConsent state), remove the injected script elements (the gtag script and the inline script added in loadPerformanceCookies), clear and/or delete window.dataLayer and the global gtag function, and reset performanceScriptsLoadedRef.current so cookies can be reloaded later; also invoke the GTM/gtag consent API (e.g., update analytics_storage to "denied") if available before removing scripts to stop further sends. Ensure this logic references performanceConsent, loadPerformanceCookies, and performanceScriptsLoadedRef so it runs on consent revocation and cleanly disables the GA runtime.
🧹 Nitpick comments (1)
services/analytics/mixpanel.ts (1)
40-53: Consider wrapping Mixpanel calls in try-catch for resilience.If
mixpanel.initthrows (e.g., due to an ad blocker or browser extension interference), the error will propagate and could disrupt the application. Analytics failures should not affect core functionality.♻️ Proposed defensive wrapper
export const initAnalytics = (): boolean => { const token = MIXPANEL_TOKEN; if (!isAnalyticsEnabled() || hasInitialized || !token) { return false; } + try { mixpanel.init(token, { autocapture: false, persistence: "localStorage", track_pageview: false, }); hasInitialized = true; return true; + } catch { + return false; + } };Similar pattern could be applied to
track,identify, andresetfunctions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/analytics/mixpanel.ts` around lines 40 - 53, The initAnalytics function currently calls mixpanel.init directly and can throw; wrap the mixpanel.init call in a try-catch inside initAnalytics, set/leave hasInitialized only on success, return false on error, and log the caught error (so analytics failures don't bubble up). Apply the same defensive pattern (try-catch, no-ops or safe returns, and logging) to the other Mixpanel wrappers: track, identify, and reset to ensure any mixpanel.* exceptions are contained.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/providers/MixpanelSetup.tsx`:
- Around line 33-47: The page-view useEffect (referencing lastTrackedPathRef and
trackPageView) can fire before the user is identified, causing the first "Page
Viewed" to be anonymous; update the effect so that when
lastTrackedPathRef.current !== pathname and hasConsent is true you first call
your identification helper (e.g., identify or identifyUser with
connectedProfile?.id and profile traits) before calling trackPageView, or
alternatively move the identify logic ahead of this effect; ensure you only
identify once per session (guarded by connectedProfile?.id and a flag/ref) and
then set lastTrackedPathRef.current = pathname and call trackPageView so the
initial page event is attributed to the logged-in profile.
---
Outside diff comments:
In `@components/cookies/CookieConsentContext.tsx`:
- Around line 78-103: Add a teardown path that reverses what
loadPerformanceCookies does when performanceConsent becomes false: when
performanceConsent transitions to false (watch the performanceConsent state),
remove the injected script elements (the gtag script and the inline script added
in loadPerformanceCookies), clear and/or delete window.dataLayer and the global
gtag function, and reset performanceScriptsLoadedRef.current so cookies can be
reloaded later; also invoke the GTM/gtag consent API (e.g., update
analytics_storage to "denied") if available before removing scripts to stop
further sends. Ensure this logic references performanceConsent,
loadPerformanceCookies, and performanceScriptsLoadedRef so it runs on consent
revocation and cleanly disables the GA runtime.
---
Nitpick comments:
In `@services/analytics/mixpanel.ts`:
- Around line 40-53: The initAnalytics function currently calls mixpanel.init
directly and can throw; wrap the mixpanel.init call in a try-catch inside
initAnalytics, set/leave hasInitialized only on success, return false on error,
and log the caught error (so analytics failures don't bubble up). Apply the same
defensive pattern (try-catch, no-ops or safe returns, and logging) to the other
Mixpanel wrappers: track, identify, and reset to ensure any mixpanel.*
exceptions are contained.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d051dc0f-8c4b-4777-b3da-32a84d3966d4
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
.github/workflows/build-upload-deploy-prod.yml__tests__/components/providers/MixpanelSetup.test.tsx__tests__/services/analytics/mixpanel.test.tscomponents/cookies/CookieConsentContext.tsxcomponents/providers/MixpanelSetup.tsxcomponents/providers/Providers.tsxconfig/env.schema.tspackage.jsonservices/analytics/mixpanel.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@services/analytics/mixpanel.ts`:
- Around line 97-100: The track call currently spreads properties after the
explicit path which allows properties.path to override the function argument;
update the object so the function argument takes precedence by placing the
spread before the path (or by removing/ignoring properties.path and then setting
path to the function argument) in the track("Page Viewed", {...}) call to ensure
the argument `path` cannot be overwritten by `properties.path`.
- Around line 83-91: The reset() function currently clears identifiedDistinctId
and calls mixpanel.reset() but leaves hasInitialized true so isAnalyticsReady()
still returns true; update reset() to also set hasInitialized = false (after
calling mixpanel.reset()) so future calls to
isAnalyticsReady()/track()/identify() will be blocked until re-initialization;
ensure you reference the hasInitialized flag and the isAnalyticsReady(),
reset(), mixpanel.reset(), track(), and identify() symbols when making the
change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 809db27a-2e22-4e70-ac46-85696bebd1a8
📒 Files selected for processing (1)
services/analytics/mixpanel.ts
Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
♻️ Duplicate comments (1)
components/providers/MixpanelSetup.tsx (1)
23-32:⚠️ Potential issue | 🟠 MajorPremature
disableAnalytics()call on mount when consent is still loading.Per context snippet 4,
performanceConsentinitializes toundefinedinCookieConsentContext. On first render,hasConsentevaluates tofalse, triggeringdisableAnalytics()before the actual consent value loads from the cookie. This setsisTrackingAllowed = falseand callsmixpanel.reset(), potentially suppressing analytics for users who already granted consent until the nextinitAnalytics()call.Consider distinguishing between "consent denied" (
performanceConsent === false) and "consent not yet loaded" (performanceConsent === undefined):Proposed fix
const hasConsent = performanceConsent === true; + const consentLoaded = performanceConsent !== undefined; useEffect(() => { - if (!hasConsent) { + if (!consentLoaded) { + return; // Wait for consent state to load + } + + if (!hasConsent) { disableAnalytics(); lastTrackedPathRef.current = null; identifiedProfileIdRef.current = null; return; } initAnalytics(); - }, [hasConsent]); + }, [hasConsent, consentLoaded]);
🧹 Nitpick comments (2)
__tests__/components/providers/MixpanelSetup.test.tsx (2)
11-11: Minor type inconsistency in mock.The mock type
{ id: number }differs from the actualApiIdentity.idtype which isstring | null(pergenerated/models/ApiIdentity.ts). While the test still validates correct behavior since the component converts to string, consider aligning the mock type for clarity:Suggested change
-let connectedProfile: { id: number } | null = null; +let connectedProfile: { id: string } | null = null;And update test values:
- connectedProfile = { id: 42 }; + connectedProfile = { id: "42" };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@__tests__/components/providers/MixpanelSetup.test.tsx` at line 11, The test mock variable connectedProfile currently types id as number; change its type to match ApiIdentity.id (string | null) by typing connectedProfile as { id: string | null } | null (or reuse ApiIdentity type) and update any numeric test values to string values (or "null" cases) so test data aligns with generated/models/ApiIdentity.ts; ensure tests still exercise the component's conversion logic where it casts id to string.
51-58: Consider adding a test for consent loading state.This test verifies
disableAnalyticsis called whenperformanceConsentisundefined, which validates current behavior. However, if the consent-loading race condition is fixed (distinguishingundefinedfromfalse), this test would need updating. Consider adding a test case for the transition fromundefined→trueto verify analytics initializes correctly after consent loads.Example additional test
it("initializes analytics when consent transitions from loading to granted", () => { performanceConsent = undefined; const { rerender } = render(<MixpanelSetup />); // Should not initialize while loading expect(initAnalyticsMock).not.toHaveBeenCalled(); performanceConsent = true; pathname = "/waves"; rerender(<MixpanelSetup />); expect(initAnalyticsMock).toHaveBeenCalledTimes(1); expect(trackPageViewMock).toHaveBeenCalledWith("/waves", { has_connected_profile: false, }); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@__tests__/components/providers/MixpanelSetup.test.tsx` around lines 51 - 58, Add a test in the MixpanelSetup suite that simulates the consent-loading transition: render <MixpanelSetup /> with the mocked performanceConsent initially set to undefined and assert initAnalyticsMock and trackPageViewMock have not been called, then set performanceConsent = true, set pathname (e.g., "/waves"), call rerender(<MixpanelSetup />) and assert initAnalyticsMock was called once and trackPageViewMock was called with the new pathname and the expected props (e.g., { has_connected_profile: false }); reference the existing mocks initAnalyticsMock, trackPageViewMock, identifyMock, disableAnalyticsMock and use rerender to trigger the component update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@__tests__/components/providers/MixpanelSetup.test.tsx`:
- Line 11: The test mock variable connectedProfile currently types id as number;
change its type to match ApiIdentity.id (string | null) by typing
connectedProfile as { id: string | null } | null (or reuse ApiIdentity type) and
update any numeric test values to string values (or "null" cases) so test data
aligns with generated/models/ApiIdentity.ts; ensure tests still exercise the
component's conversion logic where it casts id to string.
- Around line 51-58: Add a test in the MixpanelSetup suite that simulates the
consent-loading transition: render <MixpanelSetup /> with the mocked
performanceConsent initially set to undefined and assert initAnalyticsMock and
trackPageViewMock have not been called, then set performanceConsent = true, set
pathname (e.g., "/waves"), call rerender(<MixpanelSetup />) and assert
initAnalyticsMock was called once and trackPageViewMock was called with the new
pathname and the expected props (e.g., { has_connected_profile: false });
reference the existing mocks initAnalyticsMock, trackPageViewMock, identifyMock,
disableAnalyticsMock and use rerender to trigger the component update.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d167f036-3170-443b-bc65-1a3f7a424f81
📒 Files selected for processing (4)
__tests__/components/providers/MixpanelSetup.test.tsx__tests__/services/analytics/mixpanel.test.tscomponents/providers/MixpanelSetup.tsxservices/analytics/mixpanel.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/services/analytics/mixpanel.test.ts



Summary by CodeRabbit
New Features
Tests