Skip to content

Commit eb5b7b7

Browse files
committed
fix: fetch projects iteratively
1 parent 490bbbd commit eb5b7b7

5 files changed

Lines changed: 77 additions & 12 deletions

File tree

scripts/fetchData.ts

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { GraphQLClient, gql } from 'graphql-request';
22
import fs from 'fs';
33
import path from 'path';
4-
import { AllDataQuery } from '../generated/types';
4+
import { ProjectsPageQuery, StaticDataQuery } from '../generated/types';
5+
6+
type FetchedData = StaticDataQuery & { publicProjects: ProjectsPageQuery['publicProjects'] };
57

68
const datadir = path.join(__dirname, '../fullData');
79
const baseUrl = process.env.MAPSWIPE_API_ENDPOINT || 'http://localhost:8000/';
@@ -12,7 +14,7 @@ const pipelineType = process.env.PIPELINE_TYPE;
1214

1315
const graphQLClient = new GraphQLClient(GRAPHQL_ENDPOINT);
1416

15-
const dummyData: AllDataQuery = {
17+
const dummyData: FetchedData = {
1618
publicProjects: {
1719
results: [],
1820
totalCount: 0,
@@ -29,15 +31,17 @@ const dummyData: AllDataQuery = {
2931
globalExportAssets: [],
3032
};
3133

32-
const query = gql`
33-
query AllData {
34+
const PROJECT_PAGE_SIZE = 500;
35+
36+
const projectsQuery = gql`
37+
query ProjectsPage($limit: Int!, $offset: Int!) {
3438
publicProjects(
3539
filters: {
3640
status: {
3741
inList: [PUBLISHED, FINISHED],
3842
},
3943
},
40-
pagination: { limit: 9999 },
44+
pagination: { limit: $limit, offset: $offset },
4145
) {
4246
results {
4347
id
@@ -175,6 +179,11 @@ const query = gql`
175179
}
176180
totalCount
177181
}
182+
}
183+
`;
184+
185+
const staticQuery = gql`
186+
query StaticData {
178187
communityStats {
179188
id
180189
totalContributors
@@ -225,8 +234,32 @@ async function getCsrfTokenValue() {
225234
return undefined;
226235
}
227236

237+
const MAX_RETRIES = 3;
238+
239+
async function requestWithRetry<T>(
240+
requestFn: () => Promise<T>,
241+
): Promise<T> {
242+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
243+
try {
244+
// eslint-disable-next-line no-await-in-loop
245+
return await requestFn();
246+
} catch (err: unknown) {
247+
const status = (err as { response?: { status?: number } })?.response?.status;
248+
const isServerError = status !== undefined && status >= 500;
249+
if (isServerError && attempt < MAX_RETRIES) {
250+
// eslint-disable-next-line no-console
251+
console.warn(`Request failed with ${status} (attempt ${attempt}/${MAX_RETRIES}). Retrying...`);
252+
} else {
253+
throw err;
254+
}
255+
}
256+
}
257+
// unreachable, but satisfies the return type
258+
throw new Error('Unexpected end of requestWithRetry');
259+
}
260+
228261
async function fetchAndWriteData() {
229-
let data = {} as AllDataQuery;
262+
let data = {} as FetchedData;
230263
if (pipelineType === 'ci') {
231264
data = dummyData;
232265
} else {
@@ -245,7 +278,33 @@ async function fetchAndWriteData() {
245278
graphQLClient.setHeader('X-CSRFToken', csrfTokenValue);
246279
graphQLClient.setHeader('Cookie', `${COOKIE_NAME}=${csrfTokenValue}`);
247280
graphQLClient.setHeader('Referer', referer);
248-
data = (await graphQLClient.request(query)) as AllDataQuery;
281+
282+
const staticData = (await requestWithRetry(() => graphQLClient.request(staticQuery))) as Pick<
283+
FetchedData,
284+
'communityStats' | 'publicOrganizations' | 'globalExportAssets'
285+
>;
286+
287+
const allProjects: FetchedData['publicProjects']['results'] = [];
288+
let totalCount = Infinity;
289+
// eslint-disable-next-line no-await-in-loop
290+
for (let offset = 0; offset < totalCount; offset += PROJECT_PAGE_SIZE) {
291+
// eslint-disable-next-line no-await-in-loop
292+
const page = (await requestWithRetry(() => graphQLClient.request(projectsQuery, {
293+
limit: PROJECT_PAGE_SIZE,
294+
offset,
295+
}))) as Pick<FetchedData, 'publicProjects'>;
296+
allProjects.push(...page.publicProjects.results);
297+
totalCount = page.publicProjects.totalCount;
298+
console.log(`Fetched ${allProjects.length}/${totalCount} projects`);
299+
}
300+
301+
data = {
302+
...staticData,
303+
publicProjects: {
304+
results: allProjects,
305+
totalCount,
306+
},
307+
};
249308
}
250309

251310
// ensure the `data` directory exists

src/components/ProjectsMap/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ import {
2929

3030
import GestureHandler from 'components/LeafletGestureHandler';
3131
import Link from 'components/Link';
32-
import { AllDataQuery } from 'generated/types';
32+
import { ProjectsPageQuery } from 'generated/types';
3333

3434
import styles from './styles.module.css';
3535

36-
type PublicProject = NonNullable<NonNullable<AllDataQuery['publicProjects']>['results']>[number];
36+
type PublicProject = ProjectsPageQuery['publicProjects']['results'][number];
3737

3838
const pathOptions: {
3939
[key in ProjectStatus]?: CircleMarkerOptions

src/pages/[locale]/data/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ import useDebouncedValue from 'hooks/useDebouncedValue';
5050
import { GlobalExportAssets } from 'utils/queries';
5151
import data from 'fullData/staticData.json';
5252

53-
import { AllDataQuery } from 'generated/types';
53+
import { ProjectsPageQuery, StaticDataQuery } from 'generated/types';
5454
import i18nextConfig from '@/next-i18next.config';
5555

5656
import styles from './styles.module.css';
5757

58+
type AllDataQuery = StaticDataQuery & { publicProjects: ProjectsPageQuery['publicProjects'] };
59+
5860
type PublicProjects = NonNullable<NonNullable<AllDataQuery['publicProjects']>['results']>;
5961
type PublicProject = PublicProjects[number];
6062

src/pages/[locale]/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ import data from 'fullData/staticData.json';
2727

2828
import i18nextConfig from '@/next-i18next.config';
2929

30-
import { AllDataQuery } from 'generated/types';
30+
import { ProjectsPageQuery, StaticDataQuery } from 'generated/types';
3131

3232
import styles from './styles.module.css';
3333

34+
type AllDataQuery = StaticDataQuery & { publicProjects: ProjectsPageQuery['publicProjects'] };
35+
3436
async function getAllData() {
3537
// FIXME: This should be inferred
3638
return data as AllDataQuery;

src/pages/[locale]/projects/[id].tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ import {
5252
} from 'utils/chart';
5353
import { UrlInfo } from 'utils/queries';
5454

55-
import { AllDataQuery } from 'generated/types';
55+
import { ProjectsPageQuery, StaticDataQuery } from 'generated/types';
5656
import i18nextConfig from '@/next-i18next.config';
5757

5858
import styles from './styles.module.css';
5959

60+
type AllDataQuery = StaticDataQuery & { publicProjects: ProjectsPageQuery['publicProjects'] };
61+
6062
type PublicProjects = NonNullable<NonNullable<AllDataQuery['publicProjects']>['results']>;
6163
async function getAllProjects() {
6264
return (data as AllDataQuery)?.publicProjects?.results as unknown as PublicProjects;

0 commit comments

Comments
 (0)