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);
}
| if (decisionEventDispatched) { | ||
| optimizely.track(PAGE_VIEW_EVENT_NAME); | ||
| } | ||
|
|
||
| notifyDecision(flagKey); |
There was a problem hiding this comment.
I've addressed this point in 2d7b39a
Following some AI prompting here's a bit more info on why this fix appears to be correct from the AI; let me know if you think this incorrect, it made sense to me 🤔 :
We've addressed this with a URL-keyed idempotency check — a module-level
Set<string>(trackedPageViewUrls) that ensurestrack('page-views')fires at most once per page URL, regardless of how many experiments evaluate on that page.
One thing worth clarifying for context:
track()is not experiment-scoped. It fires a generic conversion event and Optimizely's backend handles attribution — it looks up every experiment the user is enrolled in and attributes that single event to all of them. So calling it once per page is the correct behaviour; calling it once per DECISION (the previous behaviour) would have inflated metrics by crediting each experiment with N pageviews for N experiments on the same page.
The
Setis also URL-aware for SPA navigation: a new pathname gets a fresh entry, so each page load still gets exactly onepage-viewsevent.
8c5e17e to
ae87b57
Compare
…per URL and on navigation to new URLs [copilot]
| } | ||
| } | ||
|
|
||
| notifyDecision(flagKey); |
| // send the visit before the page view so the ratio window is open | ||
| // this keeps the page view inside the visit denominator window in optimizely | ||
| optimizely.track(VISIT_EVENT_NAME); |
There was a problem hiding this comment.
Can you help me with this @LilyL0u ? Does the visit really need to be sent before the pageview? Obviously that poses problems for this PR as a whole if so
| }); | ||
|
|
||
| it('should call Optimizely track function for Article Page on page render', async () => { | ||
| it('should call onReady but not track any events when visit tracking is disabled', async () => { |
There was a problem hiding this comment.
Does this mean - doesn't track any events when the user is not in an experiment? Not sure what is meant by 'when visit tracking is disabled' and what is disabling it here.
There was a problem hiding this comment.
Oh is it for the event 'visit' that is being sent?
There was a problem hiding this comment.
| it('should call onReady but not track any events when visit tracking is disabled', async () => { | |
| it('should not track any events when visit tracking is disabled', async () => { |
yeah, I guess it checks the visit event isn't sent when visit tracking is disabled. Previously this will have sent view tracking without a visit.
Is this wording better?
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('page-views')into the notification listener inwithOptimizelyProvider. The event is now sent directly when a decision is received withdecisionEventDispatched: trueand an activevariationKey, removing the dependency on theoptimizelyDecisionStorerendering pipeline.trackedPageViewUrls: Set<string>) to ensuretrack('page-views')fires 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.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.PageViewTrackingis updated accordingly — it no longer trackspage-viewsdirectly and instead focuses solely on optionalvisitevent tracking.Testing
Useful Links