Skip to content

Move pageview tracking to OptimizelyProvider#14124

Open
andrewscfc wants to merge 22 commits into
latestfrom
missing-page-view-change
Open

Move pageview tracking to OptimizelyProvider#14124
andrewscfc wants to merge 22 commits into
latestfrom
missing-page-view-change

Conversation

@andrewscfc

@andrewscfc andrewscfc commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Resolves JIRA:

This PR tries out a suggestion from an Optimizely consultant, I'm not 100% sure if this code structure is clean long term but I'm happy to change it to debug the page view events we lose.

Below is an AI generated PR description verified by me


Bug: Optimizely page-views events not sent consistently

The experiment is evaluated client-side via useDecision from @optimizely/react-sdk, which fires a DECISION notification that the listener in withOptimizelyProvider subscribes to.

Previously, track('page-views') was called from the PageViewTracking component, which only rendered when OptimizelyPageMetrics read an active flag key from the optimizelyDecisionStore. The store was only populated when decisionEventDispatched was true — a flag the SDK sets only when it has successfully dispatched an impression event to Optimizely's servers. If that flag was false for any reason, the store was never updated, OptimizelyPageMetrics returned null, and no page-views event fired.

Fix:

  • Move both track('visit') and track('page-views') into the notification listener in withOptimizelyProvider. Both events are now sent directly when a decision is received with decisionEventDispatched: true and an active variationKey, removing the dependency on the optimizelyDecisionStore rendering pipeline. visit is the ratio metric denominator and page-views is the numerator — sending them in the same synchronous call sequence guarantees visit always precedes page-views, ensuring page-views fall within Optimizely's 48-hour attribution window for the ratio metric. Visit session logic (30-minute inactivity window via localStorage) is encapsulated in a new visitTracking.ts module.
  • Add a URL-keyed idempotency check (trackedPageViewUrls: Set<string>) to ensure both events fire at most once per page URL. A DECISION notification fires once per decide() call — not once per page load — so without this guard, multiple experiments on the same page would each trigger track(), inflating metrics. Note: track() is not experiment-scoped; Optimizely's backend attributes a single event to all experiments the user is enrolled in, so one call per page is correct. The Set is URL-aware for SPA navigation: a new pathname gets a fresh entry so each page still gets exactly one event.
  • Add an onClient() early return at the top of the listener to prevent any module-level state mutation (notifyDecision, trackedPageViewUrls, localStorage) during SSR.
  • Remove the decisionEventDispatched guard from notifyDecision. notifyDecision now runs on every eligible decision (active flagKey and variationKey !== 'off'), which is safe because notifyDecision already has its own idempotency check (activatedExperiments.has(flagKey)).
  • Remove the newswb_ws_topic_discovery_module special case, which allowed tracking even when variationKey was 'off'. The listener now applies a single consistent rule.
  • Delete PageViewTracking entirely and remove the trackPageView and trackVisit props from OptimizelyPageMetrics.

Testing

  1. List the steps required to test this PR.

Useful Links

Copilot AI left a comment

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.

Pull request overview

This PR fixes inconsistent Optimizely view-event tracking by ensuring Simorgh records experiment activation on every Optimizely DECISION notification, rather than only when Optimizely reports that it successfully dispatched an impression event.

Changes:

  • Removes the decisionEventDispatched property from the DECISION notification payload typing.
  • Removes the decisionEventDispatched gate so notifyDecision(flagKey) runs whenever an eligible decision is received.
Comments suppressed due to low confidence (1)

src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx:57

  • The decision notification listener change isn’t covered by the existing withOptimizelyProvider tests. Given this PR’s goal (ensuring tracking renders even when Optimizely’s decisionEventDispatched is false), it would be valuable to add a unit test that mocks notificationCenter.addNotificationListener, invokes the registered callback with a DECISION payload where decisionInfo.flagKey is set and decisionInfo.decisionEventDispatched is false, and asserts notifyDecision(flagKey) is still called exactly once (and is idempotent on subsequent calls).
optimizely?.notificationCenter?.addNotificationListener(
  enums.NOTIFICATION_TYPES.DECISION,
  (
    notification: ListenerPayload & {
      decisionInfo?: {
        flagKey?: string;
        variationKey?: string;
      };
    },
  ) => {
    const flagKey = notification.decisionInfo?.flagKey;
    const variationKey = notification.decisionInfo?.variationKey;

    if (
      flagKey &&
      (variationKey !== 'off' || flagKey === 'newswb_ws_topic_discovery_module')
    ) {
      notifyDecision(flagKey);
    }

@andrewscfc andrewscfc changed the title Remove unused decisionEventDispatched property from notification list… Move pageview tracking to OptimizelyProvider Jun 19, 2026
@andrewscfc andrewscfc requested a review from Copilot June 19, 2026 08:26

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

@andrewscfc andrewscfc force-pushed the missing-page-view-change branch from 8c5e17e to ae87b57 Compare June 19, 2026 09:00

Copilot AI left a comment

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.

Pull request overview

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

Comment thread src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx Outdated
Comment thread src/app/components/OptimizelyPageMetrics/PageViewTracking/index.tsx Outdated
Comment thread src/app/components/OptimizelyPageMetrics/PageViewTracking/index.test.tsx Outdated

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment thread src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx Outdated
Comment on lines +90 to +92
Both events are tracked from the `DECISION` notification listener in [`withOptimizelyProvider`](../../legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx), not from this component. Tracking them from the listener ensures the visit (denominator) is always sent before the page view (numerator) on the same page load, so the page view falls inside Optimizely's 48 hour attribution window for the denominator. Tracking from the listener also guarantees the events fire for every activated experiment, independent of this component's render lifecycle.

For page views per visit, enable both `trackPageView` and `trackVisit`. Visit events are emitted from the page view tracker to preserve ordering and avoid duplicate visits.
Both events are sent at most once per URL. Visit counting is session based: a new visit is counted when there has been at least 30 minutes of inactivity since the last tracked page view, and each tracked page view rolls the activity window forward so that continued browsing keeps the same visit.

@andrewscfc andrewscfc Jun 30, 2026

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.

de069ec

The solution now includes search so the doc is accurate

@andrewscfc andrewscfc requested a review from Copilot June 26, 2026 16:31

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Both events are tracked from the `DECISION` notification listener in [`withOptimizelyProvider`](../../legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx), not from this component. Tracking them from the listener ensures the visit (denominator) is always sent before the page view (numerator) on the same page load, so the page view falls inside Optimizely's 48 hour attribution window for the denominator. Tracking from the listener also guarantees the events fire for every activated experiment, independent of this component's render lifecycle.

For page views per visit, enable both `trackPageView` and `trackVisit`. Visit events are emitted from the page view tracker to preserve ordering and avoid duplicate visits.
Both events are sent at most once per URL. Visit counting is session based: a new visit is counted when there has been at least 30 minutes of inactivity since the last tracked page view, and each tracked page view rolls the activity window forward so that continued browsing keeps the same visit.

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.

Comment thread src/app/components/OptimizelyPageMetrics/index.tsx

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 don't think this readme should contain any info about page view and visits, it might get confusing

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 think your point aligns with your other one around renaming this component, can we address it at the same time?

I think this helps a bit in that some metrics you'd expect to be in here have moved; I'd like the final version of this to have a better name and focus on the depth/completion metrics exclusively.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread src/app/components/OptimizelyPageMetrics/README.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@andrewscfc andrewscfc marked this pull request as ready for review June 30, 2026 10:57
@andrewscfc andrewscfc requested a review from LilyL0u June 30, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants