Skip to content

[DNM] WS-1097 Referrer based reordering of OJ components#13459

Merged
LilyL0u merged 108 commits intolatestfrom
WS-1097-referrer-based-ordering-of-oj-components
Jan 27, 2026
Merged

[DNM] WS-1097 Referrer based reordering of OJ components#13459
LilyL0u merged 108 commits intolatestfrom
WS-1097-referrer-based-ordering-of-oj-components

Conversation

@LilyL0u
Copy link
Contributor

@LilyL0u LilyL0u commented Nov 21, 2025

https://bbc.atlassian.net/browse/WS-1097

This pull request refactors the Recommendations component to support multiple types of recommendation sources (Most Read, Top Stories, Features, Related Content) based on the user's referrer variant, and introduces new helper functions for extracting and mapping data from various content block formats. It also updates tests to cover these new variants and improves event tracking granularity.

It also duplicates the top stories and features components so that they are both inside the secondaryColumn, where they will render on desktop, and outside the secondaryColumn into a reordering function where they can have their order swapped around based on the referrerVariant. There are tests for this function.

display:none is used in order to hide the duplicated under-the-article components when the user is viewing at desktop width, and hiding the secondary column components when on mobile width. When we move this into production, we can decide whether we want to do this in a different way with a media query that decided whether the components render at all, instead of just hiding them with CSS.

Refactoring and Feature Expansion:

  • The Recommendations component now accepts new props (blocks, topStoriesContent, featuresContent, referrerVariant, and experimentProps) and dynamically selects the data source and section title based on the referrerVariant, supporting adaptive experiments for search, direct, and social referrers. [1] [2]
  • Added helper functions in helpers/index.tsx for extracting and mapping recommendation data from Optimo blocks, Features, and Top Stories, ensuring consistent structure and alt text extraction for images.

Testing Improvements:

  • Expanded tests in index.test.tsx to cover rendering of recommendations for all referrer variants, including checks for correct titles, links, images, and alt text. Also added tests for single-item scenarios and rerendering with different variants.

Event Tracking Enhancements:

  • Event tracking data now includes a groupTracker and per-item itemTracker for more granular analytics, with experiment props merged in if present. This is passed down to each RecommendationsItem.

Summary

A very high-level summary of easily-reproducible changes that can be understood by non-devs, and why these changes where made.

Code changes

  • A bullet point list of key code changes that have been made.

Developer Checklist

  • UX
    • UX Criteria met (visual UX & screenreader UX)
  • Accessibility
    • Accessibility Acceptance Criteria met
    • Accessibility swarm completed
    • Component Health updated
    • P1 accessibility bugs resolved
    • P2/P3 accessibility bugs planned (if not resolved)
  • Security
    • Security issues addressed
    • Threat Model updated
  • Documentation
    • Docs updated (runbook, READMEs)
  • Testing
    • Feature tested on relevant environments
  • Comms
    • Relevant parties notified of changes

Testing

  • Manual Testing required?
    • Local (Ready-For-Test, Local)
    • Test (Ready-For-Test, Test)
    • Preview (Ready-For-Test, Preview)
    • Live (Ready-For-Test, Live)
  • Manual Testing complete?
    • Local
    • Test
    • Preview
    • Live

Additional Testing Steps

  1. List the steps required to test this PR.

Useful Links

@LilyL0u LilyL0u changed the title mid article wip, related content WS-1097 Referrer based reordering of OJ components Nov 21, 2025
LilyL0u and others added 28 commits November 21, 2025 23:54
…ve mostRead variable, not sure what it did before...
<RelatedContentSection
content={articleBlocks}
{...(referrerVariant && {
experimentProps: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this object being used in the RelatedContentSection? I can see it as an optional prop in the type defintion, but it doesn't appear to be getting used in the view tracker.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Me and @pvaliani were talking about this on Friday. I wanted to look into it more, because we ARE getting related content clicks through in optimizely despite the experimentProps not being used

Copy link
Contributor Author

@LilyL0u LilyL0u Jan 26, 2026

Choose a reason for hiding this comment

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

I mean, we were in previous experiments. Anyway, in the branch off of this one that adds the oj-clicks event, Pedram has added code to use the experimentProps

Copy link
Contributor Author

Choose a reason for hiding this comment

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

these things passed to be into components in a few different ways (as we talked about in daily sync). Makes it a bit confusing

Copy link
Contributor

Choose a reason for hiding this comment

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

They were used in the time of day experiments, but removed once they were concluded: https://github.com/bbc/simorgh/pull/13589/changes#diff-dd4bcc7b6d8e420fd12a0efc3bd4dc7a8b103d0f6c2c75c7ef6df612dd564c7f

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah I see. I am going through each component now and making sure they have everything

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Went through each component relavent to this experiment and saw that they have both view and click events from them:

Screenshot 2026-01-27 at 09 55 22

@LilyL0u LilyL0u force-pushed the WS-1097-referrer-based-ordering-of-oj-components branch from aa19c62 to f04331a Compare January 26, 2026 18:32
Copilot AI review requested due to automatic review settings January 27, 2026 14:22
Copy link
Contributor

@amoore108 amoore108 left a comment

Choose a reason for hiding this comment

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

Thanks for all the hard work on this, very complex thing and something we can look at trying to simplify (in general, not this particular one).

Copy link
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

This PR implements a referrer-based adaptive experiment that reorders onward journey (OJ) components based on how users arrived at an article page. The experiment shows different content recommendations to users from search, social, and direct traffic sources.

Changes:

  • Introduces a referrer-based experiment that dynamically selects which OJ component to display mid-article based on referrer variant (Most Read for control, Related Content for search, Top Stories for direct, Features for social)
  • Duplicates Top Stories and Features components to render in both secondary column (desktop) and under article content (mobile/tablet), using CSS display properties to show/hide based on viewport
  • Enhances event tracking to include experiment metadata and granular item-level tracking data

Reviewed changes

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

Show a summary per file
File Description
ws-nextjs-app/cypress/e2e/testsForAllPages.ts Filters visible images to avoid testing hidden duplicate components
ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/topStories.ts Updates selectors to target only visible components for analytics tests
ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/relatedContent.ts Updates selectors to target only visible components for analytics tests
ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/featuresAnalysis.ts Updates selectors to target only visible components for analytics tests
src/app/pages/ArticlePage/index.test.tsx Updates test to handle multiple images with same alt text from duplicated components
src/app/pages/ArticlePage/helpers/index.tsx Implements component reordering logic based on referrer variant and referrer type
src/app/pages/ArticlePage/helpers/index.test.tsx Tests component ordering for different referrer variants and edge cases
src/app/pages/ArticlePage/SecondaryColumn.tsx Passes experiment props to Top Stories and Features components
src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/types.ts Adds type definitions for promo headline blocks and image structures
src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/TopStoriesItem/index.tsx Removes ts-expect-error now that types are properly defined
src/app/pages/ArticlePage/ContinueReadingButton/index.tsx Accepts and includes experiment props in event tracking data
src/app/pages/ArticlePage/ArticlePage.tsx Implements referrer experiment setup, component duplication, and experiment prop distribution
src/app/pages/ArticlePage/ArticlePage.styles.ts Adds CSS styles to hide/show duplicate components based on viewport width
src/app/models/types/global.ts Adds referrer field to ComponentExperimentProps type
src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx Exports getReferrer function for use in other components
src/app/contexts/RequestContext/index.tsx Formatting change (blank line)
src/app/components/TopBarOJs/Promo/index.tsx Removes ts-expect-error now that types are properly defined
src/app/components/RelatedContentSection/index.tsx Accepts and includes experiment props in event tracking data
src/app/components/Recommendations/index.tsx Implements adaptive content selection based on referrer variant and adds granular tracking
src/app/components/Recommendations/index.test.tsx Adds comprehensive tests for all referrer variants and data sources
src/app/components/Recommendations/helpers/index.tsx Implements data extraction and mapping functions for different content sources
src/app/components/Recommendations/fixtures.ts Adds fixture data for testing different content sources
src/app/components/Recommendations/RecommendationsItem/index.tsx Accepts event tracking data prop instead of using hardcoded value
src/app/components/MostRead/index.tsx Accepts experiment props and merges them into event tracking data

articleBlocks: OptimoBlock[];
grey2: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pageStyles: Record<string, any>;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The any type should be avoided. Consider using a more specific type for pageStyles, such as importing the styles object type or defining an interface that matches the expected structure.

Copilot uses AI. Check for mistakes.
};
images?: {
defaultPromoImage?: {
blocks?: any[];
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The any type should be avoided. Consider defining a specific type for the blocks array structure based on the OptimoBlock type or the actual structure used.

Copilot uses AI. Check for mistakes.
// --- Shared utilities for extracting image data from defaultPromoImage ---

const getAltTextFromDefaultPromoImage = (defaultPromoImage?: {
blocks?: any[];
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The any type should be avoided. Consider defining a specific type for the blocks array parameter based on the structure being accessed.

Copilot uses AI. Check for mistakes.
);
};

const getRawImageBlock = (defaultPromoImage?: { blocks?: any[] }) =>
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The any type should be avoided. Consider defining a specific type for the blocks array based on the expected image block structure.

Copilot uses AI. Check for mistakes.
return slice(0, 4, storyPromoItems);
};

export const getHeadlineFromOptimoBlock = (block: any) => {
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The any type should be avoided. Consider using the OptimoBlock type or a more specific type for the block parameter.

Copilot uses AI. Check for mistakes.
return assetUriFirst || assetUriSecond;
};

export const getAltTextFromOptimoBlock = (block: any) =>
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The any type should be avoided. Consider using the OptimoBlock type or a more specific type for the block parameter.

Copilot uses AI. Check for mistakes.
Comment on lines +163 to +166
export const getImageFromOptimoBlock = (block: any) => {
const imageBlock = block?.model?.blocks?.find((b: any) => b.type === 'image');
const rawImageBlock = imageBlock?.model?.blocks?.find(
(b: any) => b.type === 'rawImage',
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

Multiple uses of any type in this function. Consider using the OptimoBlock type or defining specific types for imageBlock and rawImageBlock.

Copilot uses AI. Check for mistakes.
};
};

export const mapOptimoBlockToRecommendation = (block: any): Recommendation => ({
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The any type should be avoided. Consider using the OptimoBlock type or a more specific type for the block parameter.

Copilot uses AI. Check for mistakes.
Comment on lines +264 to +265
const { indexImage } = item as any;

Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The as any type assertion should be avoided. Consider extending the TopStoryItem type to include the indexImage property if it's a valid field.

Suggested change
const { indexImage } = item as any;
type IndexImage = {
href?: string;
altText?: string;
width?: number;
height?: number;
copyrightHolder?: string;
originCode?: string;
};
type TopStoryItemWithOptionalIndexImage = TopStoryItem & {
indexImage?: IndexImage;
};
const { indexImage } = item as TopStoryItemWithOptionalIndexImage;

Copilot uses AI. Check for mistakes.
Boolean,
);
}
// Default for when referrerVariant is 'control', 'off', '', or any unknown value and when referrer_variant is 'adaptive_variant' but referrer is 'search''
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

There is an extra closing quote in the comment. The word "search" has two single quotes at the end instead of one.

Suggested change
// Default for when referrerVariant is 'control', 'off', '', or any unknown value and when referrer_variant is 'adaptive_variant' but referrer is 'search''
// Default for when referrerVariant is 'control', 'off', '', or any unknown value and when referrer_variant is 'adaptive_variant' but referrer is 'search'

Copilot uses AI. Check for mistakes.
@LilyL0u LilyL0u merged commit 7240cb4 into latest Jan 27, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants