Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const RecommendationsItem = ({
const { isLite } = use(RequestContext);

const clickTrackerHandler = useClickTrackerHandler(eventTrackingData);

console.log('recommendation item: ', recommendation);
if (!recommendation) return null;

const { title, image, href } = recommendation;
Expand Down
131 changes: 131 additions & 0 deletions src/app/components/Recommendations/helpers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import pathOr from 'ramda/src/pathOr';
import pathEq from 'ramda/src/pathEq';
import tail from 'ramda/src/tail';
import slice from 'ramda/src/slice';
import last from 'ramda/src/last';
import filter from 'ramda/src/filter';
import pipe from 'ramda/src/pipe';
import { OptimoBlock } from '#app/models/types/optimo';
import { Recommendation } from '#app/models/types/onwardJourney';

// Extracts up to 6 related content items from Optimo blocks, skipping custom title if present
export const getRelatedContentData = (blocks: OptimoBlock[]) => {
const BLOCKS_TO_IGNORE = ['wsoj', 'mpu', 'continueReading'];
const removeCustomBlocks = pipe(
filter((block: OptimoBlock) => !BLOCKS_TO_IGNORE.includes(block.type)),
last,
);
const relatedContentBlock = removeCustomBlocks(blocks);
if (
!relatedContentBlock ||
!pathEq('relatedContent', ['type'], relatedContentBlock)
) {
return [];
}
const items = pathOr([], ['model', 'blocks'], relatedContentBlock);
const hasCustomTitle =
pathEq('title', [0, 'type'], items) &&
pathOr(
'',
[0, 'model', 'blocks', 0, 'model', 'blocks', 0, 'model', 'text'],
items,
);
const storyPromoItems = hasCustomTitle ? tail(items) : items;
return slice(0, 6, storyPromoItems);
};

export const getHeadlineFromOptimoBlock = (block: any) => {
const headlineFirst = pathOr<string>(
'',
['model', 'blocks', 0, 'model', 'blocks', 0, 'model', 'text'],
block,
);
const headlineSecond = pathOr<string>(
'',
['model', 'blocks', 1, 'model', 'blocks', 0, 'model', 'text'],
block,
);
return headlineFirst || headlineSecond;
};

export const getHrefFromOptimoBlock = (block: any) => {
const assetUriFirst = pathOr<string>(
'',
[
'model',
'blocks',
0,
'model',
'blocks',
0,
'model',
'blocks',
0,
'model',
'locator',
],
block,
);
const assetUriSecond = pathOr<string>(
'',
[
'model',
'blocks',
1,
'model',
'blocks',
0,
'model',
'blocks',
0,
'model',
'locator',
],
block,
);
return assetUriFirst || assetUriSecond;
};

export const getAltTextFromOptimoBlock = (block: any) =>
pathOr<string>(
'',
[
'model',
'blocks',
0,
'model',
'blocks',
0,
'model',
'blocks',
0,
'model',
'blocks',
0,
'model',
'text',
],
block,
);

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',
);
return {
locator: rawImageBlock?.model?.locator ?? '',
altText: getAltTextFromOptimoBlock(block),
width: rawImageBlock?.model?.width ?? 0,
height: rawImageBlock?.model?.height ?? 0,
copyrightHolder: rawImageBlock?.model?.copyrightHolder ?? '',
originCode: rawImageBlock?.model?.originCode ?? '',
};
};

export const mapOptimoBlockToRecommendation = (block: any): Recommendation => ({
id: block.id,
title: getHeadlineFromOptimoBlock(block),
href: getHrefFromOptimoBlock(block),
image: getImageFromOptimoBlock(block),
});
66 changes: 58 additions & 8 deletions src/app/components/Recommendations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,49 @@
import { ServiceContext } from '#contexts/ServiceContext';
import useViewTracker from '#app/hooks/useViewTracker';
import { Recommendation } from '#app/models/types/onwardJourney';
import RecommendationsItem from './RecommendationsItem';
import { OptimoBlock } from '#app/models/types/optimo';
import styles from './index.styles';
import RecommendationsItem from './RecommendationsItem';
import {
getRelatedContentData,
mapOptimoBlockToRecommendation,
} from './helpers';

const eventTrackingData = {
componentName: 'midarticle-mostread',
};

const Recommendations = ({ data }: { data: Recommendation[] }) => {
interface RecommendationsProps {
data: Recommendation[];
blocks: OptimoBlock[];
topStoriesContent?: unknown;
featuresContent?: unknown;
referrerExperimentVariant?: string;
}

const Recommendations = ({
data, // control
blocks, // search
topStoriesContent, // direct
featuresContent, // social
referrerExperimentVariant, // experiment variant for referrer
}: RecommendationsProps) => {
const { recommendations, mostRead, script, service, dir } =

Check failure on line 39 in src/app/components/Recommendations/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (22.x)

'mostRead' is declared but its value is never read.

Check failure on line 39 in src/app/components/Recommendations/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (22.x)

'mostRead' is declared but its value is never read.

Check failure on line 39 in src/app/components/Recommendations/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (22.x)

'mostRead' is declared but its value is never read.

Check failure on line 39 in src/app/components/Recommendations/index.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

'mostRead' is declared but its value is never read.
use(ServiceContext);

// eslint-disable-next-line no-console
console.log('Recommendations data:', data);
// eslint-disable-next-line no-console
console.log('Recommendations blocks:', blocks);
// eslint-disable-next-line no-console
console.log('Recommendations topStoriesContent:', topStoriesContent);
// eslint-disable-next-line no-console
console.log('Recommendations featuresContent:', featuresContent);
// eslint-disable-next-line no-console
console.log(
'Recommendations referrerExperimentVariant:',
referrerExperimentVariant,
);
const viewTracker = useViewTracker(eventTrackingData);

const {
Expand All @@ -28,9 +60,27 @@

const { enabled } = useToggle('midArticleOnwardJourney');

const { hasMostRead } = mostRead || {};

if (!enabled || !hasMostRead || !data?.length) return null;
let displayData: Recommendation[] = [];

if (
!referrerExperimentVariant ||
referrerExperimentVariant === 'off' ||
referrerExperimentVariant.includes('control')
) {
displayData = data ?? [];
} else if (referrerExperimentVariant === 'search') {
displayData = getRelatedContentData(blocks).map(
mapOptimoBlockToRecommendation,
);
console.log('displayData from related content: ', displayData);
// Log the nested blocks for inspection
} else if (referrerExperimentVariant === 'direct') {
displayData = Array.isArray(topStoriesContent) ? topStoriesContent : [];
} else if (referrerExperimentVariant === 'social') {
displayData = Array.isArray(featuresContent) ? featuresContent : [];
}

if (!enabled || !displayData.length) return null;

const labelId = 'recommendations-heading';

Expand All @@ -50,7 +100,7 @@

const terms = { '%title%': title };

const isSinglePromo = data?.length === 1;
const isSinglePromo = displayData.length === 1;

const endTextId = `end-of-recommendations`;

Expand Down Expand Up @@ -85,10 +135,10 @@
</SectionLabel>
) : null}
{isSinglePromo ? (
<RecommendationsItem recommendation={data?.[0]} />
<RecommendationsItem recommendation={displayData?.[0]} />
) : (
<ul css={styles.recommendationsList} role="list" {...viewTracker}>
{data?.map(recommendation => (
{displayData?.map(recommendation => (
<li key={recommendation.id} role="listitem">
<RecommendationsItem recommendation={recommendation} />
</li>
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/RelatedContentSection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type Props = {

const RelatedContentSection = ({ content, experimentProps }: Props) => {
const { translations, script, service } = use(ServiceContext);

console.log('in related content component: ', content);
const {
palette: { GREY_2 },
} = useTheme();
Expand Down
42 changes: 38 additions & 4 deletions src/app/pages/ArticlePage/ArticlePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,27 @@ const getMpuComponent =
(allowAdvertising: boolean) => (props: ComponentToRenderProps) =>
allowAdvertising ? <AdContainer {...props} slotType="mpu" /> : null;

const getWsojComponent = (
props: ComponentToRenderProps & { data: Recommendation[] },
) => <Recommendations data={props.data} />;
const getWsojComponent = ({
data,
blocks,
topStoriesContent,
featuresContent,
referrerExperimentVariant,
}: {
data: Recommendation[];
blocks: OptimoBlock[];
topStoriesContent?: unknown;
featuresContent?: unknown;
referrerExperimentVariant?: string;
}) => (
<Recommendations
data={data} // the original data passed in before the experiment (most read), to be used for control
blocks={blocks} // the same data passed into related content, to be used for search segment
topStoriesContent={topStoriesContent} // top stories to be used for 'direct' segment
featuresContent={featuresContent} // features to be used for social segment
referrerExperimentVariant={referrerExperimentVariant}
/>
);

const DisclaimerWithPaddingOverride = (props: ComponentToRenderProps) => (
<Disclaimer {...props} increasePaddingOnDesktop={false} />
Expand Down Expand Up @@ -246,6 +264,13 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
experimentType: ExperimentType.CLIENT_SIDE,
});

// EXPERIMENT: Referrer Experiment
const referrerExperimentName = 'newswb_ws_oj_by_referrer';
let referrerExperimentVariant = useOptimizelyVariation({
experimentName: referrerExperimentName,
experimentType: ExperimentType.CLIENT_SIDE,
});
referrerExperimentVariant = 'search';
const allowAdvertising = pageData?.metadata?.allowAdvertising ?? false;
const adcampaign = pageData?.metadata?.adCampaignKeyword;

Expand All @@ -266,6 +291,8 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
const aboutTags = getAboutTags(pageData);
const topics = pageData?.metadata?.topics ?? [];
const blocks = pageData?.content?.model?.blocks ?? [];
const topStoriesContent = pageData?.secondaryColumn?.topStories;
const featuresContent = pageData?.secondaryColumn?.features;
const startsWithHeading = blocks?.[0]?.type === 'headline' || false;
const bylineBlock = blocks.find(
block => block.type === 'byline',
Expand Down Expand Up @@ -351,7 +378,14 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
group: gist,
links: ScrollablePromo,
mpu: getMpuComponent(allowAdvertising),
wsoj: getWsojComponent,
wsoj: ({ data }: { data: Recommendation[] }) =>
getWsojComponent({
data,
blocks,
topStoriesContent,
featuresContent,
referrerExperimentVariant,
}),
disclaimer: DisclaimerWithPaddingOverride,
podcastPromo: getPodcastPromoComponent(podcastPromoEnabled),
...(showContinueReadingButton && {
Expand Down
Loading