diff --git a/dotcom-rendering/src/components/ArticleBody.tsx b/dotcom-rendering/src/components/ArticleBody.tsx index 797004a8fe1..2d7e127e4ad 100644 --- a/dotcom-rendering/src/components/ArticleBody.tsx +++ b/dotcom-rendering/src/components/ArticleBody.tsx @@ -20,7 +20,6 @@ type Props = { format: ArticleFormat; blocks: Block[]; pinnedPost?: Block; - adTargeting: AdTargeting; host?: string; pageId: string; webTitle: string; @@ -123,7 +122,6 @@ export const ArticleBody = ({ format, blocks, pinnedPost, - adTargeting, host, pageId, webTitle, @@ -183,7 +181,6 @@ export const ArticleBody = ({ format={format} blocks={blocks} pinnedPost={pinnedPost} - adTargeting={adTargeting} host={host} pageId={pageId} webTitle={webTitle} @@ -239,7 +236,6 @@ export const ArticleBody = ({ { const { article, format, renderingTarget } = props; + + const adTargeting = buildAdTargeting({ + isAdFreeUser: article.isAdFreeUser, + isSensitive: article.config.isSensitive, + edition: article.config.edition, + section: article.config.section, + sharedAdTargeting: article.config.sharedAdTargeting, + adUnit: article.config.adUnit, + }); + return ( { isDev={!!article.config.isDev} /> + + + )} {renderingTarget === 'Apps' ? ( diff --git a/dotcom-rendering/src/components/FrontPage.tsx b/dotcom-rendering/src/components/FrontPage.tsx index cb2e80d58c4..65d336a3e49 100644 --- a/dotcom-rendering/src/components/FrontPage.tsx +++ b/dotcom-rendering/src/components/FrontPage.tsx @@ -2,6 +2,7 @@ import { css, Global } from '@emotion/react'; import { brandAlt, focusHalo, neutral } from '@guardian/source-foundations'; import { StrictMode } from 'react'; import { FrontLayout } from '../layouts/FrontLayout'; +import { buildAdTargeting } from '../lib/ad-targeting'; import { filterABTestSwitches } from '../model/enhance-switches'; import type { NavType } from '../model/extract-nav'; import type { DCRFrontType } from '../types/front'; @@ -12,6 +13,7 @@ import { FocusStyles } from './FocusStyles.importable'; import { Island } from './Island'; import { Metrics } from './Metrics.importable'; import { SetABTests } from './SetABTests.importable'; +import { SetAdTargeting } from './SetAdTargeting.importable'; import { ShowHideContainers } from './ShowHideContainers.importable'; import { SkipTo } from './SkipTo'; @@ -29,6 +31,15 @@ type Props = { * @param {NAVType} props.NAV - The article JSON data * */ export const FrontPage = ({ front, NAV }: Props) => { + const adTargeting = buildAdTargeting({ + isAdFreeUser: front.isAdFreeUser, + isSensitive: front.config.isSensitive, + edition: front.config.edition, + section: front.config.section, + sharedAdTargeting: front.config.sharedAdTargeting, + adUnit: front.config.adUnit, + }); + return ( { isDev={!!front.config.isDev} /> + + + ); diff --git a/dotcom-rendering/src/components/LiveBlock.stories.tsx b/dotcom-rendering/src/components/LiveBlock.stories.tsx index 31c31201b4a..bbd8a201bd5 100644 --- a/dotcom-rendering/src/components/LiveBlock.stories.tsx +++ b/dotcom-rendering/src/components/LiveBlock.stories.tsx @@ -64,10 +64,6 @@ export const VideoAsSecond = () => { return ( { return ( { return ( { return ( { return ( { return ( { return ( { return ( { return ( { return ( { return ( { return ( { { { { { { { { + setAdTargeting(adTargeting); + log('commercial', '🎯 Ad targeting', adTargeting); + return null; +}; diff --git a/dotcom-rendering/src/components/TagFrontPage.tsx b/dotcom-rendering/src/components/TagFrontPage.tsx index d186de41777..e694d8cdab6 100644 --- a/dotcom-rendering/src/components/TagFrontPage.tsx +++ b/dotcom-rendering/src/components/TagFrontPage.tsx @@ -2,6 +2,7 @@ import { css, Global } from '@emotion/react'; import { brandAlt, focusHalo, neutral } from '@guardian/source-foundations'; import { StrictMode } from 'react'; import { TagFrontLayout } from '../layouts/TagFrontLayout'; +import { buildAdTargeting } from '../lib/ad-targeting'; import { filterABTestSwitches } from '../model/enhance-switches'; import type { NavType } from '../model/extract-nav'; import type { DCRTagFrontType } from '../types/tagFront'; @@ -12,6 +13,7 @@ import { FocusStyles } from './FocusStyles.importable'; import { Island } from './Island'; import { Metrics } from './Metrics.importable'; import { SetABTests } from './SetABTests.importable'; +import { SetAdTargeting } from './SetAdTargeting.importable'; import { SkipTo } from './SkipTo'; type Props = { @@ -28,6 +30,15 @@ type Props = { * @param {NAVType} props.NAV - The article JSON data * */ export const TagFrontPage = ({ tagFront, NAV }: Props) => { + const adTargeting = buildAdTargeting({ + isAdFreeUser: tagFront.isAdFreeUser, + isSensitive: tagFront.config.isSensitive, + edition: tagFront.config.edition, + section: tagFront.config.section, + sharedAdTargeting: tagFront.config.sharedAdTargeting, + adUnit: tagFront.config.adUnit, + }); + return ( { isDev={!!tagFront.config.isDev} /> + + + ); diff --git a/dotcom-rendering/src/components/YoutubeBlockComponent.importable.tsx b/dotcom-rendering/src/components/YoutubeBlockComponent.importable.tsx index c71a456c143..b006f7b514d 100644 --- a/dotcom-rendering/src/components/YoutubeBlockComponent.importable.tsx +++ b/dotcom-rendering/src/components/YoutubeBlockComponent.importable.tsx @@ -7,6 +7,7 @@ import { useEffect, useState } from 'react'; import { trackVideoInteraction } from '../client/ga/ga'; import { record } from '../client/ophan/ophan'; import { useAB } from '../lib/useAB'; +import { useAdTargeting } from '../lib/useAdTargeting'; import type { RoleType } from '../types/content'; import { Caption } from './Caption'; @@ -25,7 +26,6 @@ type Props = { url: string; width: number; }[]; - adTargeting?: AdTargeting; isMainMedia?: boolean; height?: number; width?: number; @@ -83,7 +83,6 @@ export const YoutubeBlockComponent = ({ posterImage, expired, role, - adTargeting, isMainMedia, height = 259, width = 460, @@ -95,6 +94,8 @@ export const YoutubeBlockComponent = ({ undefined, ); + const adTargeting = useAdTargeting(duration); + const abTests = useAB(); const abTestsApi = abTests?.api; const imaEnabled = diff --git a/dotcom-rendering/src/layouts/CommentLayout.tsx b/dotcom-rendering/src/layouts/CommentLayout.tsx index 964412cea21..57f133f5781 100644 --- a/dotcom-rendering/src/layouts/CommentLayout.tsx +++ b/dotcom-rendering/src/layouts/CommentLayout.tsx @@ -39,7 +39,6 @@ import { Standfirst } from '../components/Standfirst'; import { StickyBottomBanner } from '../components/StickyBottomBanner.importable'; import { SubMeta } from '../components/SubMeta'; import { SubNav } from '../components/SubNav.importable'; -import { buildAdTargeting } from '../lib/ad-targeting'; import { getSoleContributor } from '../lib/byline'; import { canRenderAds } from '../lib/canRenderAds'; import { getContributionsServiceUrl } from '../lib/contributions'; @@ -279,16 +278,6 @@ export const CommentLayout = ({ const isInEuropeTest = article.config.abTests.europeNetworkFrontVariant === 'variant'; - const adTargeting: AdTargeting = buildAdTargeting({ - isAdFreeUser: article.isAdFreeUser, - isSensitive: article.config.isSensitive, - videoDuration: article.config.videoDuration, - edition: article.config.edition, - section: article.config.section, - sharedAdTargeting: article.config.sharedAdTargeting, - adUnit: article.config.adUnit, - }); - const showBodyEndSlot = parse(article.slotMachineFlags ?? '').showBodyEnd || article.config.switches.slotBodyEnd; @@ -449,7 +438,6 @@ export const CommentLayout = ({ { const isInEuropeTest = article.config.abTests.europeNetworkFrontVariant === 'variant'; - const adTargeting: AdTargeting = buildAdTargeting({ - isAdFreeUser: article.isAdFreeUser, - isSensitive: article.config.isSensitive, - videoDuration: article.config.videoDuration, - edition: article.config.edition, - section: article.config.section, - sharedAdTargeting: article.config.sharedAdTargeting, - adUnit: article.config.adUnit, - }); const contributionsServiceUrl = getContributionsServiceUrl(article); const palette = decidePalette(format); @@ -493,7 +484,6 @@ export const NewsletterSignupLayout = ({ article, NAV, format }: Props) => { { const isInEuropeTest = article.config.abTests.europeNetworkFrontVariant === 'variant'; - const adTargeting: AdTargeting = buildAdTargeting({ - isAdFreeUser: article.isAdFreeUser, - isSensitive: article.config.isSensitive, - videoDuration: article.config.videoDuration, - edition: article.config.edition, - section: article.config.section, - sharedAdTargeting: article.config.sharedAdTargeting, - adUnit: article.config.adUnit, - }); - const showBodyEndSlot = parse(article.slotMachineFlags ?? '').showBodyEnd || article.config.switches.slotBodyEnd; @@ -537,7 +526,6 @@ export const StandardLayout = (props: WebProps | AppProps) => { { format={format} blocks={article.blocks} pinnedPost={article.pinnedPost} - adTargeting={adTargeting} host={host} pageId={article.pageId} webTitle={article.webTitle} diff --git a/dotcom-rendering/src/lib/ArticleRenderer.tsx b/dotcom-rendering/src/lib/ArticleRenderer.tsx index 2cf2f75988f..5346f59f464 100644 --- a/dotcom-rendering/src/lib/ArticleRenderer.tsx +++ b/dotcom-rendering/src/lib/ArticleRenderer.tsx @@ -27,7 +27,6 @@ const adStylesDynamic = css` type Props = { format: ArticleFormat; elements: FEElement[]; - adTargeting?: AdTargeting; host?: string; pageId: string; webTitle: string; @@ -49,7 +48,6 @@ type Props = { export const ArticleRenderer = ({ format, elements, - adTargeting, host, pageId, webTitle, @@ -74,7 +72,6 @@ export const ArticleRenderer = ({ key={index} format={format} element={element} - adTargeting={adTargeting} ajaxUrl={ajaxUrl} host={host} index={index} diff --git a/dotcom-rendering/src/lib/LiveBlogRenderer.tsx b/dotcom-rendering/src/lib/LiveBlogRenderer.tsx index b65ede4f590..2c114b8f5f2 100644 --- a/dotcom-rendering/src/lib/LiveBlogRenderer.tsx +++ b/dotcom-rendering/src/lib/LiveBlogRenderer.tsx @@ -17,7 +17,6 @@ import type { TagType } from '../types/tag'; type Props = { format: ArticleFormat; blocks: Block[]; - adTargeting: AdTargeting; pinnedPost?: Block; host?: string; pageId: string; @@ -45,7 +44,6 @@ export const LiveBlogRenderer = ({ format, blocks, pinnedPost, - adTargeting, host, pageId, webTitle, @@ -83,7 +81,6 @@ export const LiveBlogRenderer = ({ block={pinnedPost} pageId={pageId} webTitle={webTitle} - adTargeting={adTargeting} host={host} ajaxUrl={ajaxUrl} isLiveUpdate={isLiveUpdate} @@ -137,7 +134,6 @@ export const LiveBlogRenderer = ({ format={format} pageId={pageId} webTitle={webTitle} - adTargeting={adTargeting} host={host} ajaxUrl={ajaxUrl} isLiveUpdate={isLiveUpdate} diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index fd7ee6dc680..2a641604ccb 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -71,7 +71,6 @@ import { decidePalette } from './decidePalette'; type Props = { format: ArticleFormat; element: FEElement; - adTargeting?: AdTargeting; host?: string; index: number; isMainMedia: boolean; @@ -126,7 +125,6 @@ const updateRole = (el: FEElement, format: ArticleFormat): FEElement => { export const renderElement = ({ format, element, - adTargeting, host, index, hideCaption, @@ -736,7 +734,6 @@ export const renderElement = ({ key={index} hideCaption={hideCaption} role="inline" - adTargeting={adTargeting} isMainMedia={isMainMedia} id={element.id} elementId={element.elementId} @@ -784,7 +781,6 @@ const bareElements = new Set([ export const RenderArticleElement = ({ format, element, - adTargeting, ajaxUrl, host, index, @@ -804,7 +800,6 @@ export const RenderArticleElement = ({ const el = renderElement({ format, element: withUpdatedRole, - adTargeting, ajaxUrl, host, index, diff --git a/dotcom-rendering/src/lib/useAdTargeting.ts b/dotcom-rendering/src/lib/useAdTargeting.ts new file mode 100644 index 00000000000..1c5c319eeec --- /dev/null +++ b/dotcom-rendering/src/lib/useAdTargeting.ts @@ -0,0 +1,33 @@ +import { log } from '@guardian/libs'; +import { mutate } from 'swr'; +import useSWRImmutable from 'swr/immutable'; + +const key = 'ad-targeting'; +const apiPromise = new Promise(() => { + /* this never resolves */ +}); + +/** + * A hook which returns the Ad Targeting for a given page. + * + * @param videoLength allows overriding video length, when there are multiple videos on a page + */ +export const useAdTargeting = ( + videoLength?: number, +): AdTargeting | undefined => { + const { data } = useSWRImmutable(key, () => apiPromise); + + if (data && !data.disableAds && typeof videoLength === 'number') { + data.customParams['vl'] = videoLength; + log( + 'commercial', + `🎯 Ad Targeting – video length (vl) overriden to ${videoLength}`, + ); + } + + return data; +}; + +export const setAdTargeting = (adTargeting: AdTargeting): void => { + void mutate(key, adTargeting, false); +}; diff --git a/dotcom-rendering/src/server/render.article.web.tsx b/dotcom-rendering/src/server/render.article.web.tsx index 2cfd117fb37..9c32aa1c6f8 100644 --- a/dotcom-rendering/src/server/render.article.web.tsx +++ b/dotcom-rendering/src/server/render.article.web.tsx @@ -6,7 +6,6 @@ import { import { ArticlePage } from '../components/ArticlePage'; import { isAmpSupported } from '../components/Elements.amp'; import { KeyEventsContainer } from '../components/KeyEventsContainer'; -import { buildAdTargeting } from '../lib/ad-targeting'; import { ASSET_ORIGIN, generateScriptTags, @@ -261,31 +260,16 @@ export const renderBlocks = ({ ajaxUrl, isAdFreeUser, isSensitive, - videoDuration, - edition, section, - sharedAdTargeting, - adUnit, switches, keywordIds, }: FEBlocksRequest): string => { const format: ArticleFormat = decideFormat(FEFormat); - const adTargeting: AdTargeting = buildAdTargeting({ - isAdFreeUser, - isSensitive, - videoDuration, - edition, - section, - sharedAdTargeting, - adUnit, - }); - const { html, extractedCss } = renderToStringWithEmotion(