Skip to content

Commit c0d8cf1

Browse files
Merge branch 'main' into mob/inline-product-card
2 parents e023e2c + 4c93ee2 commit c0d8cf1

36 files changed

+512
-470
lines changed

apps-rendering/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
"@guardian/cdk": "61.4.0",
4747
"@guardian/content-api-models": "31.0.0",
4848
"@guardian/content-atom-model": "6.1.0",
49-
"@guardian/eslint-config": "7.0.1",
5049
"@guardian/eslint-config-typescript": "9.0.1",
5150
"@guardian/libs": "22.0.0",
5251
"@guardian/renditions": "0.2.0",

dotcom-rendering/src/client/abTesting.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,22 @@ const getClientParticipations = (): ABParticipations => {
2929

3030
return {};
3131
};
32-
const initABTesting = (): void => {
33-
const { serverSideABTests } = window.guardian.config;
3432

35-
const clientSideABTests = getClientParticipations();
36-
37-
const participations = {
38-
...clientSideABTests,
39-
...serverSideABTests,
33+
/**
34+
* Get all AB test participations, client and server side
35+
*/
36+
const getABTestParticipations = (): ABParticipations => {
37+
return {
38+
...getClientParticipations(),
39+
...window.guardian.config.serverSideABTests,
4040
};
41+
};
42+
43+
const initWindowABTesting = (): void => {
44+
const participations = getABTestParticipations();
4145

4246
window.guardian.modules.abTests = {
43-
getParticipations: () => participations,
47+
getParticipations: getABTestParticipations,
4448
isUserInTest: (testId: string) => {
4549
return !isUndefined(participations[testId]);
4650
},
@@ -50,4 +54,4 @@ const initABTesting = (): void => {
5054
};
5155
};
5256

53-
export { initABTesting };
57+
export { initWindowABTesting, getABTestParticipations };

dotcom-rendering/src/client/main.web.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void (async () => {
6161
'abTesting',
6262
() =>
6363
import(/* webpackMode: 'eager' */ './abTesting').then(
64-
({ initABTesting }) => initABTesting(),
64+
({ initWindowABTesting }) => initWindowABTesting(),
6565
),
6666
{ priority: 'critical' },
6767
);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export const DiscussionWeb = (
103103
});
104104
}, [authStatus, props.discussionApiUrl]);
105105

106-
if (!hydrated) return <Placeholder height={324} />;
106+
if (!hydrated) return <Placeholder heights={new Map([['mobile', 324]])} />;
107107

108108
return (
109109
<Discussion
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { isNonNullable, isObject } from '@guardian/libs';
2+
import { useEffect, useState } from 'react';
3+
import { decideFormat } from '../lib/articleFormat';
4+
import { getDataLinkNameCard } from '../lib/getDataLinkName';
5+
import { addDiscussionIds } from '../lib/useCommentCount';
6+
import { palette } from '../palette';
7+
import { type DCRFrontImage } from '../types/front';
8+
import { type MainMedia } from '../types/mainMedia';
9+
import type { FETrailType, TrailType } from '../types/trails';
10+
import { MoreGalleries } from './MoreGalleries';
11+
import { Placeholder } from './Placeholder';
12+
13+
type Props = {
14+
discussionApiUrl: string;
15+
absoluteServerTimes: boolean;
16+
isAdFreeUser: boolean;
17+
ajaxUrl: string;
18+
guardianBaseUrl: string;
19+
};
20+
21+
type MoreGalleriesResponse = {
22+
heading: string;
23+
trails: FETrailType[];
24+
};
25+
26+
const getMedia = (galleryCount?: number): MainMedia | undefined => {
27+
if (typeof galleryCount === 'number') {
28+
return { type: 'Gallery', count: galleryCount.toString() };
29+
}
30+
return undefined;
31+
};
32+
33+
const toGalleryTrail = (trail: FETrailType, index: number): TrailType => {
34+
const format = decideFormat(trail.format);
35+
const image: DCRFrontImage | undefined = trail.masterImage
36+
? {
37+
src: trail.masterImage,
38+
altText: '',
39+
}
40+
: undefined;
41+
42+
return {
43+
...trail,
44+
image,
45+
format,
46+
dataLinkName: getDataLinkNameCard(format, '0', index),
47+
mainMedia: getMedia(trail.galleryCount),
48+
};
49+
};
50+
51+
const buildTrails = (
52+
trails: FETrailType[],
53+
trailLimit: number,
54+
isAdFreeUser: boolean,
55+
): TrailType[] => {
56+
return trails
57+
.filter(
58+
(trailType) =>
59+
!(
60+
trailType.branding?.brandingType?.name === 'paid-content' &&
61+
isAdFreeUser
62+
),
63+
)
64+
.slice(0, trailLimit)
65+
.map(toGalleryTrail);
66+
};
67+
68+
const fetchJson = async (ajaxUrl: string): Promise<MoreGalleriesResponse> => {
69+
const url = `${ajaxUrl}/gallery/most-viewed.json?dcr=true`;
70+
const fetchResponse = await fetch(url);
71+
if (!fetchResponse.ok) {
72+
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
73+
}
74+
75+
const responseJson: unknown = await fetchResponse.json();
76+
77+
if (isObject(responseJson)) {
78+
// TODO: we need to properly validate this data in a future PR
79+
return responseJson as MoreGalleriesResponse;
80+
} else {
81+
throw new Error(
82+
'Failed to parse JSON MoreGalleriesResponse as an object',
83+
);
84+
}
85+
};
86+
87+
export const FetchMoreGalleriesData = ({
88+
discussionApiUrl,
89+
absoluteServerTimes,
90+
isAdFreeUser,
91+
ajaxUrl,
92+
guardianBaseUrl,
93+
}: Props) => {
94+
const [data, setData] = useState<MoreGalleriesResponse | undefined>(
95+
undefined,
96+
);
97+
const [error, setError] = useState<Error | undefined>(undefined);
98+
99+
useEffect(() => {
100+
fetchJson(ajaxUrl)
101+
.then((fetchedData) => {
102+
setData(fetchedData);
103+
setError(undefined);
104+
addDiscussionIds(
105+
fetchedData.trails
106+
.map((trail) => trail.discussion?.discussionId)
107+
.filter(isNonNullable),
108+
);
109+
})
110+
.catch((err) => {
111+
setError(
112+
err instanceof Error ? err : new Error('Unknown error'),
113+
);
114+
setData(undefined);
115+
});
116+
}, [ajaxUrl]);
117+
118+
if (error) {
119+
// Send the error to Sentry and then prevent the element from rendering
120+
window.guardian.modules.sentry.reportError(error, 'more-galleries');
121+
return null;
122+
}
123+
124+
if (!data?.trails) {
125+
return (
126+
<Placeholder
127+
// Since the height of the 'MoreGalleries' component could vary quite a lot
128+
// on different breakpoints, we provide a map of heights for each breakpoint
129+
// in order to prevent layout shift
130+
heights={
131+
new Map([
132+
['mobile', 1020],
133+
['mobileMedium', 1040],
134+
['mobileLandscape', 1100],
135+
['phablet', 1200],
136+
['tablet', 700],
137+
['desktop', 800],
138+
['leftCol', 740],
139+
['wide', 790],
140+
])
141+
}
142+
shouldShimmer={false}
143+
backgroundColor={palette('--onward-background')}
144+
/>
145+
);
146+
}
147+
148+
return (
149+
<MoreGalleries
150+
absoluteServerTimes={absoluteServerTimes}
151+
trails={buildTrails(data.trails, 5, isAdFreeUser)}
152+
discussionApiUrl={discussionApiUrl}
153+
guardianBaseUrl={guardianBaseUrl}
154+
/>
155+
);
156+
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const FetchOnwardsData = ({
7272
if (!data?.trails) {
7373
return (
7474
<Placeholder
75-
height={340} // best guess at typical height
75+
heights={new Map([['mobile', 340]])} // best guess at typical height
7676
shouldShimmer={false}
7777
backgroundColor={palette('--article-background')}
7878
/>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type Props = {
1010
format: ArticleFormat;
1111
};
1212

13-
const Loading = () => <Placeholder height={172} />;
13+
const Loading = () => <Placeholder heights={new Map([['mobile', 172]])} />;
1414

1515
export const GetCricketScoreboard = ({ matchUrl, format }: Props) => {
1616
const options: SWRConfiguration = {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type Props = {
1111
format: ArticleFormat;
1212
};
1313

14-
const Loading = () => <Placeholder height={800} />;
14+
const Loading = () => <Placeholder heights={new Map([['mobile', 800]])} />;
1515

1616
const cleanTeamCodes = ({
1717
name,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type Props = {
88
format: ArticleFormat;
99
};
1010

11-
const Loading = () => <Placeholder height={40} />;
11+
const Loading = () => <Placeholder heights={new Map([['mobile', 40]])} />;
1212

1313
/**
1414
* ## Why does this need to be an Island?

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ export const InteractiveBlockComponent = ({
459459
{!loaded && (
460460
<>
461461
<Placeholder // removed by HydrateInteractiveOnce
462-
height={decideHeight(role)}
462+
heights={new Map([['mobile', decideHeight(role)]])}
463463
shouldShimmer={false}
464464
/>
465465
<a

0 commit comments

Comments
 (0)