Skip to content

Commit 0e5b707

Browse files
author
Juarez Mota
committed
Merge branch 'main' into jm/new-sign-in-gate
2 parents 8d14bbb + 8a6a9d9 commit 0e5b707

File tree

8 files changed

+115
-45
lines changed

8 files changed

+115
-45
lines changed

dotcom-rendering/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@guardian/eslint-config-typescript": "9.0.1",
3838
"@guardian/identity-auth": "6.0.1",
3939
"@guardian/identity-auth-frontend": "8.1.0",
40-
"@guardian/libs": "22.5.0",
40+
"@guardian/libs": "23.0.0",
4141
"@guardian/ophan-tracker-js": "2.2.10",
4242
"@guardian/react-crossword": "6.3.0",
4343
"@guardian/shimport": "1.0.2",

dotcom-rendering/playwright.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export const PORT = isDev ? 3030 : 9000;
1111
*/
1212
export default defineConfig({
1313
testDir: './playwright/tests',
14-
// Don't run tests _within_ files in parallel as this causes flakiness locally - investigating
14+
// Don't run tests _within_ files in parallel locally as this causes flakiness
1515
// Test files still run in parallel as per the number of workers set below
16-
fullyParallel: false,
16+
fullyParallel: !!process.env.CI,
1717
// Fail the build on CI if you accidentally left test.only in the source code
1818
forbidOnly: !!process.env.CI,
1919
// Retry on CI only

dotcom-rendering/src/components/ArticlePage.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AlreadyVisited } from './AlreadyVisited.importable';
1212
import { BrazeMessaging } from './BrazeMessaging.importable';
1313
import { useConfig } from './ConfigContext';
1414
import { DarkModeMessage } from './DarkModeMessage';
15+
import { EnhanceAffiliateLinks } from './EnhanceAffiliateLinks.importable';
1516
import { FocusStyles } from './FocusStyles.importable';
1617
import { Island } from './Island';
1718
import { Lightbox } from './Lightbox';
@@ -130,6 +131,11 @@ export const ArticlePage = (props: WebProps | AppProps) => {
130131
serverSideTests={frontendData.config.abTests}
131132
/>
132133
</Island>
134+
{!!frontendData.affiliateLinksDisclaimer && (
135+
<Island priority="feature" defer={{ until: 'idle' }}>
136+
<EnhanceAffiliateLinks />
137+
</Island>
138+
)}
133139
</>
134140
)}
135141
{renderingTarget === 'Web' ? (
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useEffect } from 'react';
2+
import { getSkimlinksAccountId, isSkimlink } from '../lib/affiliateLinksUtils';
3+
4+
/**
5+
* Add custom parameters to skimlink URLs:
6+
* - referrer
7+
* - Skimlinks account ID
8+
*
9+
* ## Why does this need to be an Island?
10+
*
11+
* Code needs to be client side to get the referrer and AB test participation
12+
*
13+
* ---
14+
*
15+
* (No visual story exists as this does not render anything)
16+
*/
17+
export const EnhanceAffiliateLinks = () => {
18+
useEffect(() => {
19+
const allLinksOnPage = [...document.querySelectorAll('a')];
20+
21+
for (const link of allLinksOnPage) {
22+
if (isSkimlink(link.href)) {
23+
const referrerDomain =
24+
document.referrer === ''
25+
? 'none'
26+
: new URL(document.referrer).hostname;
27+
28+
const skimlinksAccountId = getSkimlinksAccountId(link.href);
29+
30+
// Skimlinks treats xcust as one long string, so we use | to separate values
31+
const xcustValue = `referrer|${referrerDomain}|accountId|${skimlinksAccountId}`;
32+
33+
link.href = `${link.href}&xcust=${encodeURIComponent(
34+
xcustValue,
35+
)}`;
36+
}
37+
}
38+
});
39+
40+
// We don’t render anything
41+
return null;
42+
};

dotcom-rendering/src/components/Island.test.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { CommentCount } from './CommentCount.importable';
1414
import { ConfigProvider } from './ConfigContext';
1515
import { DiscussionLayout } from './DiscussionLayout';
1616
import { DiscussionMeta } from './DiscussionMeta.importable';
17+
import { EnhanceAffiliateLinks } from './EnhanceAffiliateLinks.importable';
1718
import { EnhancePinnedPost } from './EnhancePinnedPost.importable';
1819
import { FocusStyles } from './FocusStyles.importable';
1920
import { FooterReaderRevenueLinks } from './FooterReaderRevenueLinks.importable';
@@ -166,6 +167,16 @@ describe('Island: server-side rendering', () => {
166167
).not.toThrow();
167168
});
168169

170+
test('EnhanceAffiliateLinks', () => {
171+
expect(() =>
172+
renderToString(
173+
<WithConfig>
174+
<EnhanceAffiliateLinks />
175+
</WithConfig>,
176+
),
177+
).not.toThrow();
178+
});
179+
169180
test('EnhancePinnedPost', () => {
170181
expect(() =>
171182
renderToString(

dotcom-rendering/src/components/TextBlockComponent.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { ReactNode } from 'react';
1111
import { Fragment } from 'react';
1212
import type { IOptions } from 'sanitize-html';
1313
import sanitise from 'sanitize-html';
14+
import { isSkimlink } from '../lib/affiliateLinksUtils';
1415
import {
1516
ArticleDesign,
1617
ArticleDisplay,
@@ -238,16 +239,6 @@ export const textBlockStyles = (format: ArticleFormat) => css`
238239
}
239240
`;
240241

241-
/** A function to check if a URL represents an affiliate link */
242-
const isSkimlink = (url?: string): boolean => {
243-
try {
244-
return !!url && new URL(url).host === 'go.skimresources.com';
245-
} catch (err: unknown) {
246-
// If not a valid URL, it won't be an affiliate link
247-
return false;
248-
}
249-
};
250-
251242
const buildElementTree =
252243
(format: ArticleFormat, showDropCaps: boolean) =>
253244
(node: Node, key: number): ReactNode => {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/** A function to check if a URL represents an affiliate link */
2+
export const isSkimlink = (url?: string): boolean => {
3+
try {
4+
return !!url && new URL(url).host === 'go.skimresources.com';
5+
} catch (err: unknown) {
6+
// If not a valid URL, it won't be an affiliate link
7+
return false;
8+
}
9+
};
10+
11+
/** A function to fetch the Skimlinks account ID from the URL to then pass it into the xcust*/
12+
export const getSkimlinksAccountId = (url?: string): string => {
13+
try {
14+
if (!url) return '';
15+
const parsedUrl = new URL(url);
16+
return parsedUrl.searchParams.get('id') ?? '';
17+
} catch {
18+
return '';
19+
}
20+
};

pnpm-lock.yaml

Lines changed: 32 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)