Skip to content

Commit 0af87b5

Browse files
authored
Add reducer to remove consecutive ad slots for fronts with secondary level containers (#13796)
Additionally prevents ad insertion directly before the container attached to the merch high position
1 parent e568ea2 commit 0af87b5

File tree

2 files changed

+60
-16
lines changed

2 files changed

+60
-16
lines changed

dotcom-rendering/src/lib/getFrontsAdPositions.test.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type AdCandidateMobile,
1010
getFrontsBannerAdPositions,
1111
getMobileAdPositions,
12+
removeConsecutiveAdSlotsReducer,
1213
} from './getFrontsAdPositions';
1314

1415
const defaultTestCollections: AdCandidateMobile[] = [...Array<number>(12)].map(
@@ -60,14 +61,14 @@ describe('Mobile Ads', () => {
6061
{ collectionType: 'fixed/medium/slow-VI' },
6162
{ collectionType: 'fixed/small/slow-IV' }, // Ad position (8)
6263
{ collectionType: 'fixed/small/slow-III' },
63-
{ collectionType: 'fixed/medium/fast-XII' }, // Ad position (10)
64+
{ collectionType: 'fixed/medium/fast-XII' }, // Ignored - is before merch high position
6465
{ collectionType: 'fixed/small/fast-VIII' }, // Ignored - is merch high position
6566
{ collectionType: 'news/most-popular' }, // Ignored - is most viewed container
6667
];
6768

6869
const mobileAdPositions = getMobileAdPositions(testCollections);
6970

70-
expect(mobileAdPositions).toEqual([0, 2, 4, 6, 8, 10]);
71+
expect(mobileAdPositions).toEqual([0, 2, 4, 6, 8]);
7172
});
7273

7374
// We used https://www.theguardian.com/uk as a blueprint
@@ -94,14 +95,14 @@ describe('Mobile Ads', () => {
9495
{ collectionType: 'fixed/small/slow-IV' },
9596
{ collectionType: 'fixed/small/slow-IV' }, // Ad position (19)
9697
{ collectionType: 'dynamic/slow-mpu' },
97-
{ collectionType: 'fixed/small/slow-IV' }, // Ad position (21)
98+
{ collectionType: 'fixed/small/slow-IV' }, // Ignored - is before merch high position
9899
{ collectionType: 'fixed/medium/slow-VI' }, // Ignored - is merch high position
99100
{ collectionType: 'news/most-popular' }, // Ignored - is most viewed container
100101
];
101102

102103
const mobileAdPositions = getMobileAdPositions(testCollections);
103104

104-
expect(mobileAdPositions).toEqual([0, 2, 4, 8, 11, 14, 17, 19, 21]);
105+
expect(mobileAdPositions).toEqual([0, 2, 4, 8, 11, 14, 17, 19]);
105106
});
106107

107108
// We used https://www.theguardian.com/international as a blueprint
@@ -205,14 +206,14 @@ describe('Mobile Ads', () => {
205206
{ collectionType: 'fixed/small/slow-III' },
206207
{ collectionType: 'fixed/small/slow-IV' }, // Ad position (9)
207208
{ collectionType: 'fixed/small/slow-V-half' },
208-
{ collectionType: 'fixed/small/slow-V-third' }, // Ad position (11)
209+
{ collectionType: 'fixed/small/slow-V-third' }, // Ignored - is before merch high position
209210
{ collectionType: 'fixed/small/fast-VIII' }, // Ignored - is merch high position
210211
{ collectionType: 'news/most-popular' }, // Ignored - is most viewed container
211212
];
212213

213214
const mobileAdPositions = getMobileAdPositions(testCollections);
214215

215-
expect(mobileAdPositions).toEqual([1, 3, 5, 7, 9, 11]);
216+
expect(mobileAdPositions).toEqual([1, 3, 5, 7, 9]);
216217
});
217218

218219
it('Europe Network Front, with beta containers and more than 4 collections, with thrashers in various places', () => {
@@ -235,12 +236,11 @@ describe('Mobile Ads', () => {
235236
}, // Ad position (6)
236237
{ collectionType: 'flexible/special', containerLevel: 'Primary' }, // Ignored - is before thrasher
237238
{ collectionType: 'fixed/thrasher' }, // Ad position (8)
238-
{ collectionType: 'flexible/general', containerLevel: 'Primary' }, // Ignored - is before secondary container
239-
{ collectionType: 'scrollable/small', containerLevel: 'Secondary' }, // Ad position (10)
239+
{ collectionType: 'flexible/general', containerLevel: 'Primary' }, // Ignored is consecutive ad after position 8
240240
{ collectionType: 'static/feature/2', containerLevel: 'Primary' }, // Ignored - is before secondary container
241241
{ collectionType: 'scrollable/small', containerLevel: 'Secondary' }, // Ignored - is before secondary container
242242
{ collectionType: 'scrollable/small', containerLevel: 'Secondary' }, // Ignored - is before secondary container
243-
{ collectionType: 'scrollable/small', containerLevel: 'Secondary' }, // Ad position (14)
243+
{ collectionType: 'scrollable/small', containerLevel: 'Secondary' }, // Ad position (13)
244244
{ collectionType: 'static/feature/2', containerLevel: 'Primary' }, // Ignored - is before secondary container
245245
{
246246
collectionType: 'scrollable/medium',
@@ -251,7 +251,7 @@ describe('Mobile Ads', () => {
251251
containerLevel: 'Secondary',
252252
}, // Ignored - is before secondary container
253253
{ collectionType: 'scrollable/small', containerLevel: 'Secondary' }, // Ignored - is before thrasher
254-
{ collectionType: 'fixed/thrasher' }, // Ad position (19)
254+
{ collectionType: 'fixed/thrasher' }, // Ad position (18)
255255
{ collectionType: 'flexible/general', containerLevel: 'Primary' }, // Ignored - is before secondary container
256256
{
257257
collectionType: 'scrollable/feature',
@@ -262,7 +262,7 @@ describe('Mobile Ads', () => {
262262

263263
const mobileAdPositions = getMobileAdPositions(testCollections);
264264

265-
expect(mobileAdPositions).toEqual([0, 4, 6, 8, 10, 14, 19]);
265+
expect(mobileAdPositions).toEqual([0, 4, 6, 8, 13, 18]);
266266
});
267267
});
268268

@@ -319,3 +319,23 @@ describe('Standard fronts fronts-banner ad slots', () => {
319319
expect(adPositions.length).toEqual(8);
320320
});
321321
});
322+
323+
describe('removeConsecutiveAdSlotsReducer', () => {
324+
it('removes consecutive slots from array of all consecutive numbers', () => {
325+
const arr = [0, 1, 2, 3, 4, 5];
326+
const result = arr.reduce(removeConsecutiveAdSlotsReducer, []);
327+
expect(result).toEqual([0, 2, 4]);
328+
});
329+
330+
it('removes consecutive slots from array of some consecutive numbers', () => {
331+
const arr = [0, 3, 7, 11, 12, 13, 19, 20];
332+
const result = arr.reduce(removeConsecutiveAdSlotsReducer, []);
333+
expect(result).toEqual([0, 3, 7, 11, 13, 19]);
334+
});
335+
336+
it('handles empty array', () => {
337+
const arr: number[] = [];
338+
const result = arr.reduce(removeConsecutiveAdSlotsReducer, []);
339+
expect(result).toEqual([]);
340+
});
341+
});

dotcom-rendering/src/lib/getFrontsAdPositions.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ const getMerchHighPosition = (collections: AdCandidateMobile[]): number => {
3434
const isFirstContainerAndThrasher = (collectionType: string, index: number) =>
3535
index === 0 && collectionType === 'fixed/thrasher';
3636

37-
const isMerchHighPosition = (
37+
const isMerchHighPositionOrBefore = (
3838
collectionIndex: number,
3939
merchHighPosition: number,
40-
): boolean => collectionIndex === merchHighPosition;
40+
): boolean =>
41+
collectionIndex === merchHighPosition ||
42+
collectionIndex === merchHighPosition - 1;
4143

4244
const isBeforeThrasher = (index: number, collections: AdCandidateMobile[]) =>
4345
collections[index + 1]?.collectionType === 'fixed/thrasher';
@@ -58,6 +60,22 @@ const isBeforeSecondaryLevelContainer = (
5860
const hasSecondaryLevelContainers = (collections: AdCandidateMobile[]) =>
5961
!!collections.find((c) => c.containerLevel === 'Secondary');
6062

63+
export const removeConsecutiveAdSlotsReducer = (
64+
acc: number[],
65+
slotNum: number,
66+
): number[] => {
67+
const prevSlot = acc.at(-1);
68+
if (isUndefined(prevSlot)) {
69+
// Insert first slot
70+
return [slotNum];
71+
} else if (prevSlot < slotNum - 1) {
72+
// If previous slot is more than 1 index away from current slot, we're happy
73+
return [...acc, slotNum];
74+
} else {
75+
return acc;
76+
}
77+
};
78+
6179
/**
6280
* Checks if mobile ad insertion is possible immediately after the
6381
* position of the current collection
@@ -77,13 +95,13 @@ const canInsertMobileAd =
7795
) => {
7896
/**
7997
* Ad slots can only be inserted after positions that satisfy the following rules:
80-
* - Is NOT the slot used for the merch high position
98+
* - Is NOT the slot used for the merch high position or the slot before that
8199
* - Is NOT a thrasher if it is the first container
82100
* - Is NOT before a thrasher
83101
* - Is NOT the most viewed container
84102
*/
85103
const rules = [
86-
!isMerchHighPosition(index, merchHighPosition),
104+
!isMerchHighPositionOrBefore(index, merchHighPosition),
87105
!isFirstContainerAndThrasher(collection.collectionType, index),
88106
!isBeforeThrasher(index, collections),
89107
!isMostViewedContainer(collection),
@@ -126,6 +144,13 @@ const getMobileAdPositions = (collections: AdCandidateMobile[]): number[] => {
126144
)
127145
.map((collection) => collections.indexOf(collection))
128146
.filter((adPosition: number) => adPosition !== -1)
147+
// Avoid consecutive ad slots if the front does have secondary containers
148+
.reduce(
149+
hasSecondaryContainers
150+
? removeConsecutiveAdSlotsReducer
151+
: (acc: number[], el: number) => [...acc, el], // returns the original array
152+
[],
153+
)
129154
.slice(0, MAX_FRONTS_MOBILE_ADS)
130155
);
131156
};
@@ -348,7 +373,6 @@ const getFrontsBannerAdPositions = (
348373

349374
export {
350375
isEvenIndex,
351-
isMerchHighPosition,
352376
getMerchHighPosition,
353377
getMobileAdPositions,
354378
getFrontsBannerAdPositions,

0 commit comments

Comments
 (0)