Skip to content

feat: track Stay Updated button click with PostHog cta_clicked event#3225

Merged
ahtesham-quraish merged 7 commits into
mainfrom
feat/stay-updated-posthog-tracking
Apr 22, 2026
Merged

feat: track Stay Updated button click with PostHog cta_clicked event#3225
ahtesham-quraish merged 7 commits into
mainfrom
feat/stay-updated-posthog-tracking

Conversation

@rachellougee
Copy link
Copy Markdown
Contributor

@rachellougee rachellougee commented Apr 17, 2026

What are the relevant tickets?

https://github.com/mitodl/hq/issues/10916

Description (What does it do?)

Adds PostHog cta_clicked event tracking when a user clicks the "Stay Updated" button on Course, Program product pages

How can this be tested?

same test as #3218

To test the "Stay Updated" button on your local:

  1. Go to http://mitxonline.odl.local:8013/admin
  2. Find a program (or create one) that has a product page set up
  3. Set the enrollment modes to verified (no audit mode)
  4. Make sure NEXT_PUBLIC_STAY_UPDATED_HUBSPOT_FORM_ID is set in env/frontend.local.env
NEXT_PUBLIC_STAY_UPDATED_HUBSPOT_FORM_ID=test-form-id

Then visit the corresponding program page on http://open.odl.local:8062 and the "Stay Updated" button should appear alongside the enrollment button.

image

Click on "Stay Updated" and verify cta_clicked is captured in posthog with data attribute

image image

Additional Context

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 17, 2026

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@rachellougee rachellougee marked this pull request as ready for review April 17, 2026 20:45
Copilot AI review requested due to automatic review settings April 17, 2026 20:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds missing analytics for the “Stay Updated” CTA on MITx Online course/program product pages by emitting a PostHog cta_clicked event with resource context.

Changes:

  • Add a resource prop to ProductPageTemplate and capture PostHogEvents.CallToActionClicked when “Stay Updated” is clicked.
  • Pass resource metadata (id/readable_id/type) from Course and Program product pages into the template.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
frontends/main/src/app-pages/ProductPages/ProductPageTemplate.tsx Adds PostHog capture on “Stay Updated” click and introduces optional resource prop to provide event context.
frontends/main/src/app-pages/ProductPages/CoursePage.tsx Passes course resource metadata into ProductPageTemplate for CTA tracking.
frontends/main/src/app-pages/ProductPages/ProgramPage.tsx Passes program resource metadata into ProductPageTemplate for CTA tracking.
frontends/main/src/app-pages/ProductPages/ProgramAsCoursePage.tsx Passes program resource metadata into ProductPageTemplate for CTA tracking on the course-style program page.

Comment thread frontends/main/src/app-pages/ProductPages/ProductPageTemplate.tsx Outdated
Comment thread frontends/main/src/app-pages/ProductPages/ProductPageTemplate.tsx
rachellougee and others added 3 commits April 17, 2026 17:22
… in ProductPageTemplate

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Also fix pre-existing broken 'as jest.MockedFunction' cast by using jest.mocked().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ahtesham-quraish ahtesham-quraish self-assigned this Apr 20, 2026
})

const handleStayUpdatedClick = () => {
if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY && resource) {
Copy link
Copy Markdown
Contributor

@ahtesham-quraish ahtesham-quraish Apr 20, 2026

Choose a reason for hiding this comment

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

Problem based on my assumption: posthog.capture() is called before NiceModal.show(). If posthog.capture throws (e.g., PostHog client not ready, misconfigured, or runtime error), NiceModal.show never executes — the "Stay Updated" button becomes silently non-functional to the user.

Should Either wrap with try/finally or open the modal first or it would no create issue because posthog will handle the errors internally?

expect(mockedNiceModalShow).toHaveBeenCalled()
})

describe("PostHog tracking", () => {
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.

Missing regression test for the failure path above.
Current PostHog tests in ProductPageTemplate.test.tsx:142 verify event firing and non-firing, but there is no test where capture throws while asserting the modal still opens.
Suggested test: mock capture to throw and assert NiceModal.show is still called once.

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.

this test would be misleading for two reasons:

PostHog's SDK never throws from capture. The test would encode a premise that is false for this library — it would look like it's protecting against a real risk when it isn't.

It tests implementation at the wrong layer. If PostHog's internal behavior ever changed, it would be their SDK's regression to catch, not ours.

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.

Are you saying your original comment was out of place? Let's mark this as resolved if so.

Copy link
Copy Markdown
Contributor

@ahtesham-quraish ahtesham-quraish left a comment

Choose a reason for hiding this comment

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

PR looks good. Left few comments with one question

1 . The resource prop is optional (resource?), meaning PostHog tracking silently skips when it's not provided. This is intentional and correct per the guard clause — but it means that if future callers forget to pass resource, there will be no analytics and no warning. Consider adding a console.warn in dev mode if shouldShowStayUpdatedButton is true but resource is absent?

<div>Page content</div>
</ProductPageTemplate>,
)
if (showStayUpdated) {
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.

Request: Could you simplify this to just pass showStayUpdated={showStayUpdated}?

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.

Done

expect(mockedNiceModalShow).toHaveBeenCalled()
})

describe("PostHog tracking", () => {
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.

Are you saying your original comment was out of place? Let's mark this as resolved if so.

Comment on lines +306 to +311
posthog.capture(PostHogEvents.CallToActionClicked, {
label: "Stay Updated",
resourceId: resource!.id,
readableId: resource!.readable_id,
resourceType: resource!.resource_type,
})
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.

Technical request: Let's remove the non-null assertions (!.readable_id). These are dangerous and have caused errors in the past. Ideally we should have a linting or TS rule against them.

If you add if (!showStayUpdated || !resource) return earlier in the callback, typescript will narrow the type and you can avoid the non-null asseritons.

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.

Feature Issue: I think we have a issue/problem—both here and on the enrollment button—with resourceId. This is a numeric primary key id. the issue we have is where it's coming from... In some places (resource drawer, cards, search page) it's the Learn PK, and in some places (enrollment button, stay updated) it's the MITxOnline pk.

Request: Could you address this issue, both here and in CourseEnrollmentButton.tsx, ProgramEnrollmentButton.tsx?

My suggestion for addressing would be to just remove resourceId from the enrollment button and "stay updated" clicks, though attaching it as mitxonlineId would be OK, too.

In particular, here I'd send:

{
        label: "Stay Updated",
        readableId: resource.readable_id,
        resourceType: resource.resource_type,
        platform: PlatformEnum.Mitxonline,
}

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.

done

}

export type ResourceInfo = {
id: number
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 think you could remove this id param now, right?

@ahtesham-quraish ahtesham-quraish force-pushed the feat/stay-updated-posthog-tracking branch from d2cba12 to ba31b4d Compare April 22, 2026 14:53
@ahtesham-quraish ahtesham-quraish merged commit 8ca7e65 into main Apr 22, 2026
19 of 20 checks passed
@ahtesham-quraish ahtesham-quraish deleted the feat/stay-updated-posthog-tracking branch April 22, 2026 15:35
@odlbot odlbot mentioned this pull request Apr 22, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants