Move pageview tracking to OptimizelyProvider#14124
Conversation
There was a problem hiding this comment.
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
decisionEventDispatchedproperty from the DECISION notification payload typing. - Removes the
decisionEventDispatchedgate sonotifyDecision(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
withOptimizelyProvidertests. Given this PR’s goal (ensuring tracking renders even when Optimizely’sdecisionEventDispatchedis false), it would be valuable to add a unit test that mocksnotificationCenter.addNotificationListener, invokes the registered callback with a DECISION payload wheredecisionInfo.flagKeyis set anddecisionInfo.decisionEventDispatchedisfalse, and assertsnotifyDecision(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);
}
8c5e17e to
ae87b57
Compare
…per URL and on navigation to new URLs [copilot]
…g on the client [copilot]
…iew : visit ratio metrics [copilot]
| 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. |
There was a problem hiding this comment.
The solution now includes search so the doc is accurate
…o missing-page-view-change
| 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. |
There was a problem hiding this comment.
I don't think this readme should contain any info about page view and visits, it might get confusing
There was a problem hiding this comment.
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.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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-viewsevents not sent consistentlyThe experiment is evaluated client-side via
useDecisionfrom@optimizely/react-sdk, which fires aDECISIONnotification that the listener inwithOptimizelyProvidersubscribes to.Previously,
track('page-views')was called from thePageViewTrackingcomponent, which only rendered whenOptimizelyPageMetricsread an active flag key from theoptimizelyDecisionStore. The store was only populated whendecisionEventDispatchedwastrue— a flag the SDK sets only when it has successfully dispatched an impression event to Optimizely's servers. If that flag wasfalsefor any reason, the store was never updated,OptimizelyPageMetricsreturnednull, and nopage-viewsevent fired.Fix:
track('visit')andtrack('page-views')into the notification listener inwithOptimizelyProvider. Both events are now sent directly when a decision is received withdecisionEventDispatched: trueand an activevariationKey, removing the dependency on theoptimizelyDecisionStorerendering pipeline.visitis the ratio metric denominator andpage-viewsis the numerator — sending them in the same synchronous call sequence guaranteesvisitalways precedespage-views, ensuring page-views fall within Optimizely's 48-hour attribution window for the ratio metric. Visit session logic (30-minute inactivity window vialocalStorage) is encapsulated in a newvisitTracking.tsmodule.trackedPageViewUrls: Set<string>) to ensure both events fire at most once per page URL. ADECISIONnotification fires once perdecide()call — not once per page load — so without this guard, multiple experiments on the same page would each triggertrack(), 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. TheSetis URL-aware for SPA navigation: a new pathname gets a fresh entry so each page still gets exactly one event.onClient()early return at the top of the listener to prevent any module-level state mutation (notifyDecision,trackedPageViewUrls,localStorage) during SSR.decisionEventDispatchedguard fromnotifyDecision.notifyDecisionnow runs on every eligible decision (activeflagKeyandvariationKey !== 'off'), which is safe becausenotifyDecisionalready has its own idempotency check (activatedExperiments.has(flagKey)).newswb_ws_topic_discovery_modulespecial case, which allowed tracking even whenvariationKeywas'off'. The listener now applies a single consistent rule.PageViewTrackingentirely and remove thetrackPageViewandtrackVisitprops fromOptimizelyPageMetrics.Testing
Useful Links