From 95a4da0ca5128a12896377b86a28f3abc6e0dd48 Mon Sep 17 00:00:00 2001 From: Charlotte Date: Wed, 8 Jan 2025 12:26:42 +0000 Subject: [PATCH] update mobile ad position logic for beta fronts and refactor ad insertion logic into clearer rule definitions --- .../src/lib/getFrontsAdPositions.ts | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.ts index 63e06e6871..7bd96b7519 100644 --- a/dotcom-rendering/src/lib/getFrontsAdPositions.ts +++ b/dotcom-rendering/src/lib/getFrontsAdPositions.ts @@ -14,7 +14,10 @@ type GroupedCounts = { standard: number; }; -type AdCandidate = Pick; +export type AdCandidate = Pick< + DCRCollectionType, + 'collectionType' | 'containerLevel' +>; const getMerchHighPosition = (collectionCount: number): number => collectionCount >= 4 ? 2 : 0; @@ -37,25 +40,37 @@ const isBeforeThrasher = (index: number, collections: AdCandidate[]) => const isMostViewedContainer = (collection: AdCandidate) => collection.collectionType === 'news/most-popular'; -const isSecondaryLevelContainer = (collection: AdCandidate | undefined) => - collection?.config.containerLevel === 'Secondary'; - -const isBeforeASecondaryLevelContainer = ( +const isBeforeSecondaryLevelContainer = ( index: number, collections: AdCandidate[], -) => isSecondaryLevelContainer(collections[index + 1]); - -/** Detemines whether an ad can be inserted above the current container on mobile */ -const shouldInsertAd = - (merchHighPosition: number) => - (collection: AdCandidate, index: number, collections: AdCandidate[]) => - !( - isFirstContainerAndThrasher(collection.collectionType, index) || - isMerchHighPosition(index, merchHighPosition) || - isBeforeThrasher(index, collections) || - isMostViewedContainer(collection) || - isBeforeASecondaryLevelContainer(index, collections) - ); +) => collections[index + 1]?.containerLevel === 'Secondary'; + +const hasSecondaryLevelContainers = (collections: AdCandidate[]) => + !!collections.find((c) => c.containerLevel === 'Secondary'); + +/** Determines whether an ad can be inserted below the current container on mobile */ +const canInsertAd = + (merchHighPosition: number, hasSecondaryContainers: boolean) => + (collection: AdCandidate, index: number, collections: AdCandidate[]) => { + /** Base rules are separated from beta ones to avoid changing ad placement logic for fronts without beta containers */ + const conditions = [ + !isFirstContainerAndThrasher(collection.collectionType, index), + !isMerchHighPosition(index, merchHighPosition), + !isBeforeThrasher(index, collections), + !isMostViewedContainer(collection), + ]; + + /** Beta rules exist for fronts with primary and secondary level containers */ + const betaConditions = [ + // Allow insertion after first container and prevent insertion before a secondary level container + index === 0 || !isBeforeSecondaryLevelContainer(index, collections), + ]; + + // Ad insertion is possible if every condition is met + return hasSecondaryContainers + ? [...conditions, ...betaConditions].every(Boolean) + : conditions.every(Boolean); + }; const isEvenIndex = (_collection: unknown, index: number): boolean => index % 2 === 0; @@ -66,18 +81,26 @@ const isEvenIndex = (_collection: unknown, index: number): boolean => * b. next to the merchandising-high ad slot * c. before a thrasher * d. after the Most Viewed container. + * e. before a secondary level container * After we've filtered out the containers next to which we can insert an ad, - * we take every other container, up to a maximum of 10, for targeting MPU insertion. + * we take every other container (if no secondary level containers exist), + * up to a maximum of 10, for targeting MPU insertion. */ const getMobileAdPositions = (collections: AdCandidate[]): number[] => { const merchHighPosition = getMerchHighPosition(collections.length); - - return collections - .filter(shouldInsertAd(merchHighPosition)) - .filter(isEvenIndex) - .map((collection: AdCandidate) => collections.indexOf(collection)) - .filter((adPosition: number) => adPosition !== -1) - .slice(0, MAX_FRONTS_MOBILE_ADS); + const hasSecondaryContainers = hasSecondaryLevelContainers(collections); + + return ( + collections + .filter(canInsertAd(merchHighPosition, hasSecondaryContainers)) + // Use every other ad position if the front has no secondary containers + .filter((c, i) => + hasSecondaryContainers ? true : isEvenIndex(c, i), + ) + .map((collection: AdCandidate) => collections.indexOf(collection)) + .filter((adPosition: number) => adPosition !== -1) + .slice(0, MAX_FRONTS_MOBILE_ADS) + ); }; /**