Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ import {
type Profile,
} from "api/v0"
import OnboardingPage from "./OnboardingPage"
import { usePostHog } from "posthog-js/react"
import { PostHogEvents } from "@/common/constants"

jest.mock("posthog-js/react", () => ({
...jest.requireActual("posthog-js/react"),
usePostHog: jest.fn(),
}))
const mockCapture = jest.fn()
jest.mocked(usePostHog).mockReturnValue(
// @ts-expect-error Not mocking all of posthog
{ capture: mockCapture },
)

jest.mock("next/navigation", () =>
jest.requireActual("next-router-mock/navigation"),
Expand Down Expand Up @@ -167,4 +179,41 @@ describe("OnboardingPage", () => {

expect(mockRouter.asPath).toEqual("/search?resource=184")
})

describe("PostHog tracking", () => {
beforeEach(() => {
process.env.NEXT_PUBLIC_POSTHOG_API_KEY = "test-key"
mockCapture.mockReset()
})

afterEach(() => {
delete process.env.NEXT_PUBLIC_POSTHOG_API_KEY
})

it("fires cta_clicked with label 'Next' and step when Next is clicked", async () => {
await setupAndProgressToStep(0)
await user.click(await findNextButton())
expect(mockCapture).toHaveBeenCalledWith(
PostHogEvents.CallToActionClicked,
{ label: "Next", step: 1, location: "onboarding" },
)
})

it("fires cta_clicked with label 'Finish' and step when Finish is clicked", async () => {
await setupAndProgressToStep(STEPS_DATA.length - 1)
mockCapture.mockClear()
await user.click(await findFinishButton())
expect(mockCapture).toHaveBeenCalledWith(
PostHogEvents.CallToActionClicked,
{ label: "Finish", step: STEPS_DATA.length, location: "onboarding" },
)
})

it("does not fire cta_clicked when NEXT_PUBLIC_POSTHOG_API_KEY is not set", async () => {
delete process.env.NEXT_PUBLIC_POSTHOG_API_KEY
await setupAndProgressToStep(0)
await user.click(await findNextButton())
expect(mockCapture).not.toHaveBeenCalled()
})
})
})
11 changes: 11 additions & 0 deletions frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { useEffect, useId, useMemo } from "react"
import { useRouter } from "next-nprogress-bar"
import { usePostHog } from "posthog-js/react"
import {
styled,
Step,
Expand Down Expand Up @@ -33,6 +34,7 @@ import {
ProfileSchema,
} from "@/common/profile"
import { useSearchParams } from "next/navigation"
import { PostHogEvents } from "@/common/constants"

const NUM_STEPS = 5

Expand Down Expand Up @@ -155,6 +157,7 @@ const OnboardingPage: React.FC = () => {
const { isLoading: userLoading, data: user } = useUserMe()
const [activeStep, setActiveStep] = React.useState<number>(0)
const router = useRouter()
const posthog = usePostHog()
const searchParams = useSearchParams()
const nextUrl = searchParams.get("next")

Expand All @@ -169,6 +172,14 @@ const OnboardingPage: React.FC = () => {
topic_interests: values.topic_interests.map((id) => parseInt(id)),
})
}
const label = activeStep < NUM_STEPS - 1 ? "Next" : "Finish"
if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure you need this guard here. posthog.capture is safe to call unconditionally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I kept it here because Copilot pointed out in my first PR that this PostHog capture isn’t guarded by NEXT_PUBLIC_POSTHOG_API_KEY, while most other captures in frontends/main check that env var before calling posthog.capture.”. I don’t think that guard is necessary.

#3218 (comment)

posthog.capture(PostHogEvents.CallToActionClicked, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

posthog.capture fires before mutateAsync, so if the mutation throws and error the event is already recorded even though the step wasn't actually completed. Quick fix just move this after:

      if (formik.dirty) {
        await mutateAsync({
          ...values,
          topic_interests: values.topic_interests.map((id) => parseInt(id)),
        })
      }

label,
step: activeStep + 1,
location: "onboarding",
})
}
if (activeStep < NUM_STEPS - 1) {
setActiveStep((prevActiveStep) => prevActiveStep + 1)
} else {
Expand Down
Loading