Skip to content

Commit 126a3fb

Browse files
Merge pull request #14892 from guardian/filter/utm-into-xcust
Add UTM params into xCust
2 parents 002c794 + eb4aff8 commit 126a3fb

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

dotcom-rendering/src/components/EnhanceAffiliateLinks.importable.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import { useBetaAB } from '../lib/useAB';
77
* - Referrer
88
* - Skimlinks account ID
99
* - AB test participations
10+
* - UTM parameters (from page URL or referrer URL):
11+
* - utm_source
12+
* - utm_medium
13+
* - utm_campaign
14+
* - utm_content
15+
* - utm_term
1016
*
1117
* ## Why does this need to be an Island?
1218
*
@@ -16,6 +22,26 @@ import { useBetaAB } from '../lib/useAB';
1622
*
1723
* (No visual story exists as this does not render anything)
1824
*/
25+
26+
// Helper to extract UTM parameters from URLSearchParams
27+
function getUtmString(params: URLSearchParams, keys: string[]): string {
28+
return keys
29+
.map((key) => {
30+
const value = params.get(key);
31+
return value ? `${key}|${value}` : null;
32+
})
33+
.filter(Boolean)
34+
.join('|');
35+
}
36+
37+
const utmKeys = [
38+
'utm_source',
39+
'utm_medium',
40+
'utm_campaign',
41+
'utm_content',
42+
'utm_term',
43+
];
44+
1945
export const EnhanceAffiliateLinks = () => {
2046
const abTests = useBetaAB();
2147

@@ -32,6 +58,26 @@ export const EnhanceAffiliateLinks = () => {
3258
useEffect(() => {
3359
const allLinksOnPage = [...document.querySelectorAll('a')];
3460

61+
const urlParams = new URLSearchParams(window.location.search);
62+
63+
const referrerURLParams = new URLSearchParams(
64+
document.referrer.split('?')[1] ?? '',
65+
);
66+
67+
const utmParamsFromArticleURL = getUtmString(urlParams, utmKeys);
68+
69+
const utmParamsFromReferrer = getUtmString(referrerURLParams, utmKeys);
70+
71+
/* Selects UTM parameters from the article URL if present;
72+
otherwise it falls back to UTM parameters from the referrer URL if those exist.
73+
If neither are present it returns an empty string.*/
74+
const utmParamsString =
75+
utmParamsFromArticleURL && utmParamsFromArticleURL.trim() !== ''
76+
? utmParamsFromArticleURL
77+
: utmParamsFromReferrer && utmParamsFromReferrer.trim() !== ''
78+
? utmParamsFromReferrer
79+
: '';
80+
3581
for (const link of allLinksOnPage) {
3682
if (isSkimlink(link.href)) {
3783
const referrerDomain =
@@ -44,7 +90,7 @@ export const EnhanceAffiliateLinks = () => {
4490
// Skimlinks treats xcust as one long string, so we use | to separate values
4591
const xcustValue = `referrer|${referrerDomain}|accountId|${skimlinksAccountId}${
4692
abTestString ? `|abTestParticipations|${abTestString}` : ''
47-
}`;
93+
}${utmParamsString ? `|${utmParamsString}` : ''}`;
4894

4995
link.href = `${link.href}&xcust=${encodeURIComponent(
5096
xcustValue,

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,72 @@ describe('EnhanceAffiliateLinks', () => {
8181
'xcust=referrer%7Cfoo.com%7CaccountId%7C12345%7CabTestParticipations%7Ctest1%3AvariantA%2Ctest2%3AvariantB',
8282
);
8383
});
84+
85+
it('should use article URL UTM parameters when present', () => {
86+
Object.defineProperty(window, 'location', {
87+
value: new URL(
88+
'https://example.test/this?utm_source=growth&utm_medium=epicuk&utm_campaign=q3_test&utm_content=filter_general',
89+
),
90+
configurable: true,
91+
});
92+
93+
document.body.innerHTML = `<a href="https://go.skimresources.com/?id=12345">Skimlink</a>`;
94+
95+
render(<EnhanceAffiliateLinks />);
96+
97+
const link = document.querySelector('a');
98+
expect(link?.href).toContain(
99+
'utm_source%7Cgrowth%7Cutm_medium%7Cepicuk%7Cutm_campaign%7Cq3_test%7Cutm_content%7Cfilter_general',
100+
);
101+
});
102+
103+
it('should use UTM parameters from the referrer if the article URL has none', () => {
104+
Object.defineProperty(window, 'location', {
105+
value: new URL('https://example.test/page'),
106+
configurable: true,
107+
});
108+
109+
Object.defineProperty(document, 'referrer', {
110+
value: 'https://foo.bar/some?utm_source=testsource&utm_medium=somemedium&utm_campaign=refcamp',
111+
configurable: true,
112+
});
113+
114+
document.body.innerHTML = `
115+
<a href="https://go.skimresources.com/?id=12345">Skimlink</a>
116+
`;
117+
118+
render(<EnhanceAffiliateLinks />);
119+
120+
const link = document.querySelector('a');
121+
122+
expect(link?.href).toContain(
123+
'utm_source%7Ctestsource%7Cutm_medium%7Csomemedium%7Cutm_campaign%7Crefcamp',
124+
);
125+
});
126+
127+
it('should use UTM parameters from the article URL over the referrer if both exist', () => {
128+
Object.defineProperty(window, 'location', {
129+
value: new URL(
130+
'https://example.test/page?utm_source=pagegrow&utm_medium=somemedium',
131+
),
132+
configurable: true,
133+
});
134+
135+
Object.defineProperty(document, 'referrer', {
136+
value: 'https://foo.bar?utm_source=refgrow&utm_medium=refmed',
137+
configurable: true,
138+
});
139+
140+
document.body.innerHTML = `
141+
<a href="https://go.skimresources.com/?id=12345">Skimlink</a>
142+
`;
143+
144+
render(<EnhanceAffiliateLinks />);
145+
146+
const link = document.querySelector('a');
147+
expect(link?.href).toContain(
148+
'utm_source%7Cpagegrow%7Cutm_medium%7Csomemedium',
149+
);
150+
expect(link?.href).not.toContain('refgrow');
151+
});
84152
});

0 commit comments

Comments
 (0)