diff --git a/common/app/conf/switches/ABTestSwitches.scala b/common/app/conf/switches/ABTestSwitches.scala index 902760547d1c..67791304a4e5 100644 --- a/common/app/conf/switches/ABTestSwitches.scala +++ b/common/app/conf/switches/ABTestSwitches.scala @@ -54,4 +54,14 @@ trait ABTestSwitches { sellByDate = Some(LocalDate.of(2024, 10, 30)), exposeClientSide = true, ) + + Switch( + ABTests, + "ab-us-big-events", + "Test revenue impact of using shared id in prebid", + owners = Seq(Owner.withEmail("dotcom.platform@guardian.co.uk")), + safeState = Off, + sellByDate = Some(LocalDate.of(2024, 12, 18)), + exposeClientSide = true, + ) } diff --git a/common/app/conf/switches/PerformanceSwitches.scala b/common/app/conf/switches/PerformanceSwitches.scala index 1b9dd01671f8..860086795b38 100644 --- a/common/app/conf/switches/PerformanceSwitches.scala +++ b/common/app/conf/switches/PerformanceSwitches.scala @@ -213,7 +213,7 @@ trait PerformanceSwitches { "Shorten the surrogate cache time for recent articles for load testing", owners = Seq(Owner.withEmail("dotcom.platform@theguardian.com")), safeState = Off, - sellByDate = LocalDate.of(2024, 10, 1), + sellByDate = LocalDate.of(2024, 11, 19), exposeClientSide = false, ) @@ -223,7 +223,7 @@ trait PerformanceSwitches { "Shorten the surrogate cache time for older articles for load testing", owners = Seq(Owner.withEmail("dotcom.platform@theguardian.com")), safeState = Off, - sellByDate = LocalDate.of(2024, 10, 1), + sellByDate = LocalDate.of(2024, 11, 19), exposeClientSide = false, ) } diff --git a/common/app/layout/slices/Container.scala b/common/app/layout/slices/Container.scala index b8c96e188bd1..15e211c92559 100644 --- a/common/app/layout/slices/Container.scala +++ b/common/app/layout/slices/Container.scala @@ -13,6 +13,7 @@ sealed trait Container case class Dynamic(get: DynamicContainer) extends Container case class Flexible(get: FlexibleContainer) extends Container case class Fixed(get: ContainerDefinition) extends Container +case class Scrollable(get: ContainerDefinition) extends Container case class Email(get: EmailLayout) extends Container case object NavList extends Container case object NavMediaList extends Container @@ -36,7 +37,9 @@ object Container extends GuLogging { ("news/most-popular", MostPopular), ("flexible/special", Flexible(FlexibleSpecial)), ("flexible/general", Flexible(FlexibleGeneral)), - ) ++ FixedContainers.all.mapV(Fixed.apply) ++ EmailLayouts.all.mapV(Email.apply) + ) ++ FixedContainers.all.mapV(Fixed.apply) ++ EmailLayouts.all.mapV(Email.apply) ++ ScrollableContainers.all.mapV( + Scrollable.apply, + ) /** So that we don't blow up at runtime, which would SUCK */ val default = Fixed(FixedContainers.fixedSmallSlowIV) diff --git a/common/app/layout/slices/FixedContainers.scala b/common/app/layout/slices/FixedContainers.scala index 79ea8cdad394..7ac0bbf14229 100755 --- a/common/app/layout/slices/FixedContainers.scala +++ b/common/app/layout/slices/FixedContainers.scala @@ -37,7 +37,9 @@ object FixedContainers { val showcaseSingleStories = slices(ShowcaseSingleStories) - val highlights = slices(Highlights) + val staticFeature2 = slices(StaticFeature) + + val staticMedium4 = slices(QuarterQuarterQuarterQuarter) val all: Map[String, ContainerDefinition] = Map( ("fixed/small/slow-I", slices(FullMedia75)), @@ -55,7 +57,8 @@ object FixedContainers { ("fixed/large/slow-XIV", slices(ThreeQuarterQuarter, QuarterQuarterQuarterQuarter, Ql2Ql2Ql2Ql2)), ("fixed/thrasher", thrasher), ("fixed/showcase", showcaseSingleStories), - ("scrollable/highlights", highlights), + ("static/feature/2", staticFeature2), + ("static/medium/4", staticMedium4), ) ++ (if (Configuration.faciatool.showTestContainers) Map( ( diff --git a/common/app/layout/slices/ScrollableContainers.scala b/common/app/layout/slices/ScrollableContainers.scala new file mode 100644 index 000000000000..fc1a8967c111 --- /dev/null +++ b/common/app/layout/slices/ScrollableContainers.scala @@ -0,0 +1,11 @@ +package layout.slices + +object ScrollableContainers { + import ContainerDefinition.{ofSlices => slices} + val all: Map[String, ContainerDefinition] = Map( + ("scrollable/highlights", slices(Highlights)), + ("scrollable/small", slices(ScrollableSmall)), + ("scrollable/medium", slices(ScrollableMedium)), + ("scrollable/feature", slices(ScrollableFeature)), + ) +} diff --git a/common/app/layout/slices/Slice.scala b/common/app/layout/slices/Slice.scala index 107ff24584a1..b08c010fbb25 100755 --- a/common/app/layout/slices/Slice.scala +++ b/common/app/layout/slices/Slice.scala @@ -1127,8 +1127,8 @@ case object TTMpu extends Slice { */ case object Highlights extends Slice { val layout = SliceLayout( - cssClassName = "t-t-t-t-t-t", - columns = Seq( + cssClassName = "scrollable-highlights", + columns = Seq.fill(6)( SingleItem( colSpan = 1, ItemClasses( @@ -1136,39 +1136,104 @@ case object Highlights extends Slice { tablet = MediaList, ), ), + ), + ) +} + +/* + * Scrollable slice definitions + * These layouts are implemented via a carousel in DCR. + * But due to how fronts are pressed in facia-press, we need to provide a slice definition here. + * This ensures the intended number of cards get pressed in a collection. + */ + +case object ScrollableSmall extends Slice { + val layout = SliceLayout( + cssClassName = "scrollable-small", + columns = Seq.fill(8)( SingleItem( colSpan = 1, ItemClasses( - mobile = Standard, + mobile = MediaList, tablet = MediaList, ), ), + ), + ) +} + +case object ScrollableMedium extends Slice { + val layout = SliceLayout( + cssClassName = "scrollable-medium", + columns = Seq.fill(6)( SingleItem( colSpan = 1, ItemClasses( mobile = Standard, - tablet = MediaList, + tablet = Standard, ), ), + ), + ) +} +case object ScrollableFeature extends Slice { + val layout = SliceLayout( + cssClassName = "scrollable-feature", + columns = Seq.fill(3)( SingleItem( colSpan = 1, ItemClasses( - mobile = Standard, - tablet = MediaList, + mobile = cards.FullMedia100, + tablet = cards.FullMedia100, ), ), + ), + ) +} + +/* + * The Static Feature container layout has two immersive style cards (full media) + * + * Desktop: + * .____________._____________. + * | ########## | ########## | + * | ########## | ########## | + * | ########## | ########## | + * | ########## | ########## | + * | ########## | ########## | + * '------------'-------------' + * + * Mobile: + * .____________. + * | ########## | + * | ########## | + * | ########## | + * | ########## | + * | ########## | + * '------------' + * | ########## | + * | ########## | + * | ########## | + * | ########## | + * | ########## | + * '------------' + */ +case object StaticFeature extends Slice { + val layout = SliceLayout( + cssClassName = "static-feature", + columns = Seq( SingleItem( colSpan = 1, ItemClasses( - mobile = Standard, - tablet = MediaList, + mobile = cards.FullMedia100, + tablet = cards.FullMedia100, ), ), SingleItem( colSpan = 1, ItemClasses( - mobile = Standard, - tablet = MediaList, + mobile = cards.FullMedia100, + tablet = cards.FullMedia100, ), ), ), diff --git a/facia-press/app/frontpress/FapiFrontPress.scala b/facia-press/app/frontpress/FapiFrontPress.scala index ec45e6704985..e613a15e0dec 100755 --- a/facia-press/app/frontpress/FapiFrontPress.scala +++ b/facia-press/app/frontpress/FapiFrontPress.scala @@ -323,13 +323,17 @@ trait FapiFrontPress extends EmailFrontPress with GuLogging { treats <- getTreats(collection) } yield { val storyCountTotal = curated.length + backfill.length - val storyCountMax: Int = { + val storyCountMax: Int = collection.collectionConfig.collectionType match { // nav/list stories should never be capped - if (collection.collectionConfig.collectionType == "nav/list") storyCountTotal - // scrollable/highlights container is capped at 6 stories - else if (collection.collectionConfig.collectionType == "scrollable/highlights") 6 + case "nav/list" => storyCountTotal + // scrollable feature containers are capped at 3 stories + case "scrollable/feature" => 3 + // scrollable highlights and medium containers are capped at 6 stories + case "scrollable/highlights" | "scrollable/medium" => 6 + // scrollable/small containers are capped at 8 stories + case "scrollable/small" => 8 // other container types should be capped at a maximum number of stories set in the app config - else Math.min(Configuration.facia.collectionCap, storyCountTotal) + case _ => Math.min(Configuration.facia.collectionCap, storyCountTotal) } val storyCountVisible = Container .storiesCount( diff --git a/facia/app/services/dotcomrendering/FaciaPicker.scala b/facia/app/services/dotcomrendering/FaciaPicker.scala index 348ca89977a4..6a2573957f5b 100644 --- a/facia/app/services/dotcomrendering/FaciaPicker.scala +++ b/facia/app/services/dotcomrendering/FaciaPicker.scala @@ -47,6 +47,11 @@ object FrontChecks { "scrollable/highlights", "flexible/special", "flexible/general", + "scrollable/small", + "scrollable/medium", + "scrollable/feature", + "static/feature/2", + "static/medium/4", ) def hasOnlySupportedCollections(faciaPage: PressedPage) = diff --git a/facia/test/services/dotcomrendering/FaciaPickerTest.scala b/facia/test/services/dotcomrendering/FaciaPickerTest.scala index 51c663a8b427..7b18ebad3291 100644 --- a/facia/test/services/dotcomrendering/FaciaPickerTest.scala +++ b/facia/test/services/dotcomrendering/FaciaPickerTest.scala @@ -251,6 +251,11 @@ import layout.slices.EmailLayouts PressedCollectionBuilder.mkPressedCollection("scrollable/highlights"), PressedCollectionBuilder.mkPressedCollection("flexible/special"), PressedCollectionBuilder.mkPressedCollection("flexible/general"), + PressedCollectionBuilder.mkPressedCollection("scrollable/small"), + PressedCollectionBuilder.mkPressedCollection("scrollable/medium"), + PressedCollectionBuilder.mkPressedCollection("scrollable/feature"), + PressedCollectionBuilder.mkPressedCollection("static/feature/2"), + PressedCollectionBuilder.mkPressedCollection("static/medium/4"), ), ) diff --git a/package.json b/package.json index 3f16babd846e..9790ef3fb855 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@emotion/react": "11.11.1", "@emotion/styled": "^10.0.27", "@guardian/ab-core": "8.0.0", - "@guardian/commercial": "22.1.0", + "@guardian/commercial": "22.2.0", "@guardian/core-web-vitals": "6.0.0", "@guardian/eslint-config-typescript": "9.0.1", "@guardian/identity-auth": "3.0.0", diff --git a/static/src/javascripts/projects/common/modules/experiments/ab-tests.ts b/static/src/javascripts/projects/common/modules/experiments/ab-tests.ts index af2353c38ee2..85546378e4ec 100644 --- a/static/src/javascripts/projects/common/modules/experiments/ab-tests.ts +++ b/static/src/javascripts/projects/common/modules/experiments/ab-tests.ts @@ -2,6 +2,7 @@ import type { ABTest } from '@guardian/ab-core'; import { remoteRRHeaderLinksTest } from './tests/remote-header-test'; import { signInGateMainControl } from './tests/sign-in-gate-main-control'; import { signInGateMainVariant } from './tests/sign-in-gate-main-variant'; +import { usBigEvents } from './tests/us-big-events'; // keep in sync with ab-tests in dotcom-rendering // https://github.com/guardian/dotcom-rendering/blob/main/dotcom-rendering/src/web/experiments/ab-tests.ts @@ -9,4 +10,5 @@ export const concurrentTests: readonly ABTest[] = [ signInGateMainVariant, signInGateMainControl, remoteRRHeaderLinksTest, + usBigEvents, ]; diff --git a/static/src/javascripts/projects/common/modules/experiments/tests/us-big-events.js b/static/src/javascripts/projects/common/modules/experiments/tests/us-big-events.js new file mode 100644 index 000000000000..59fe80837b59 --- /dev/null +++ b/static/src/javascripts/projects/common/modules/experiments/tests/us-big-events.js @@ -0,0 +1,23 @@ +export const usBigEvents = { + id: 'UsBigEvents', + start: '2024-10-02', + expiry: '2024-12-18', + author: 'dotcom.platform@guardian.co.uk', + description: + 'Test the impact of showing the user a component that highlights the Guardians journalism.', + audience: 0, + audienceOffset: 0, + audienceCriteria: 'US-based users that see the US edition.', + successMeasure: 'Users are more likely to engage with the site.', + canRun: () => true, + variants: [ + { + id: 'control', + test: () => {}, + }, + { + id: 'variant', + test: () => {}, + }, + ], +}; diff --git a/yarn.lock b/yarn.lock index ee271629ffe5..2483d9d3db8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4039,17 +4039,16 @@ __metadata: languageName: node linkType: hard -"@guardian/commercial@npm:22.1.0": - version: 22.1.0 - resolution: "@guardian/commercial@npm:22.1.0" +"@guardian/commercial@npm:22.2.0": + version: 22.2.0 + resolution: "@guardian/commercial@npm:22.2.0" dependencies: "@changesets/cli": "npm:^2.26.2" - "@guardian/prebid.js": "npm:8.52.0-4" + "@guardian/prebid.js": "npm:8.52.0-5" "@octokit/core": "npm:^6.1.2" fastdom: "npm:^1.0.11" lodash-es: "npm:^4.17.21" process: "npm:^0.11.10" - raven-js: "npm:^3.27.2" tslib: "npm:^2.6.2" web-vitals: "npm:^4.2.1" peerDependencies: @@ -4060,7 +4059,7 @@ __metadata: "@guardian/libs": ^18.0.0 "@guardian/source": ^8.0.0 typescript: ~5.5.3 - checksum: 10c0/df5234a3dad3a867c40a19e085431cda0497a73971e526ac88dfbaef654e223914173b5c6702cbc742b6e0df33d9ed31ce448792b9ab5d8d734bae36091a5f0a + checksum: 10c0/c9c9a51a1ed3d3d056ce073d7ced46544cd5525d0f19c6a8e963533862b28af0673a9279e2f18af03d6e02f4aad0d0b58894a398614b4a4891e8ba6917e08aa2 languageName: node linkType: hard @@ -4133,7 +4132,7 @@ __metadata: "@emotion/react": "npm:11.11.1" "@emotion/styled": "npm:^10.0.27" "@guardian/ab-core": "npm:8.0.0" - "@guardian/commercial": "npm:22.1.0" + "@guardian/commercial": "npm:22.2.0" "@guardian/core-web-vitals": "npm:6.0.0" "@guardian/eslint-config-typescript": "npm:9.0.1" "@guardian/identity-auth": "npm:3.0.0" @@ -4321,9 +4320,9 @@ __metadata: languageName: node linkType: hard -"@guardian/prebid.js@npm:8.52.0-4": - version: 8.52.0-4 - resolution: "@guardian/prebid.js@npm:8.52.0-4" +"@guardian/prebid.js@npm:8.52.0-5": + version: 8.52.0-5 + resolution: "@guardian/prebid.js@npm:8.52.0-5" dependencies: "@babel/core": "npm:^7.23.2" "@babel/plugin-transform-runtime": "npm:^7.18.9" @@ -4346,7 +4345,7 @@ __metadata: dependenciesMeta: fsevents: optional: true - checksum: 10c0/8c9b54322586c350b4b2f2c246d680368725fb44bd463fa5d0bacd1806573fe74b06838c27699c3d4bcc0001351ad84cd477a7d32f5aa69d1cdec0e1cf0daffb + checksum: 10c0/1a3bb0debe98168d3fd364425f8bc8c370fc5d6af62827a18f4c0481e31627bf9c04b799dfc6a4dcbbbe0c8db9845a9714c8649a5f1abd4f54531ecb6456f4aa languageName: node linkType: hard @@ -14956,7 +14955,7 @@ __metadata: languageName: node linkType: hard -"raven-js@npm:^3.19.1, raven-js@npm:^3.27.2": +"raven-js@npm:^3.19.1": version: 3.27.2 resolution: "raven-js@npm:3.27.2" checksum: 10c0/a0fc187d6bfb7bae90e689580ffcaee45b877475e6cd26f48986cc7cb4b4df7f0da8ad7bd406b839f1b75c963b00edbc2d00ff27710bdbfa82a02bf85e8ca915