Skip to content

Commit 07314be

Browse files
committed
Increase the data loading speed
1 parent 88b5863 commit 07314be

File tree

1 file changed

+94
-33
lines changed

1 file changed

+94
-33
lines changed

src/lib/statsProvider.tsx

Lines changed: 94 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// src/lib/statsProvider.tsx
21

32
/** @jsxImportSource react */
43
import React, {
@@ -66,7 +65,8 @@ interface CommunityStatsProviderProps {
6665

6766
const GITHUB_ORG = "recodehive";
6867
const POINTS_PER_PR = 10;
69-
const MAX_CONCURRENT_REQUESTS = 5; // Limit concurrent requests to avoid rate limiting
68+
const MAX_CONCURRENT_REQUESTS = 8; // Increased for better performance
69+
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes cache
7070

7171
export function CommunityStatsProvider({ children }: CommunityStatsProviderProps) {
7272
const {
@@ -86,6 +86,12 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps
8686
// New state for leaderboard data
8787
const [contributors, setContributors] = useState<Contributor[]>([]);
8888
const [stats, setStats] = useState<Stats | null>(null);
89+
90+
// Cache state
91+
const [cache, setCache] = useState<{
92+
data: { contributors: Contributor[]; stats: Stats } | null;
93+
timestamp: number;
94+
}>({ data: null, timestamp: 0 });
8995

9096
const fetchAllOrgRepos = useCallback(async (headers: Record<string, string>) => {
9197
const repos: any[] = [];
@@ -108,28 +114,60 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps
108114
const fetchMergedPRsForRepo = useCallback(async (repoName: string, headers: Record<string, string>) => {
109115
const mergedPRs: PullRequestItem[] = [];
110116
let page = 1;
111-
while (true) {
112-
const resp = await fetch(
113-
`https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=${page}`,
114-
{ headers }
117+
118+
// Create promises for parallel pagination
119+
const pagePromises: Promise<PullRequestItem[]>[] = [];
120+
121+
// First, get the first page to estimate total pages
122+
const firstResp = await fetch(
123+
`https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=1`,
124+
{ headers }
125+
);
126+
127+
if (!firstResp.ok) {
128+
console.warn(`Failed to fetch PRs for ${repoName}: ${firstResp.status} ${firstResp.statusText}`);
129+
return [];
130+
}
131+
132+
const firstPRs: PullRequestItem[] = await firstResp.json();
133+
if (!Array.isArray(firstPRs) || firstPRs.length === 0) return [];
134+
135+
const firstPageMerged = firstPRs.filter((pr) => Boolean(pr.merged_at));
136+
mergedPRs.push(...firstPageMerged);
137+
138+
// If we got less than 100, that's all there is
139+
if (firstPRs.length < 100) return mergedPRs;
140+
141+
// Estimate remaining pages (with a reasonable limit)
142+
const maxPages = Math.min(MAX_PAGES_PER_REPO, 10); // Conservative estimate
143+
144+
// Create parallel requests for remaining pages
145+
for (let i = 2; i <= maxPages; i++) {
146+
pagePromises.push(
147+
fetch(
148+
`https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=${i}`,
149+
{ headers }
150+
)
151+
.then(async (resp) => {
152+
if (!resp.ok) return [];
153+
const prs: PullRequestItem[] = await resp.json();
154+
if (!Array.isArray(prs)) return [];
155+
return prs.filter((pr) => Boolean(pr.merged_at));
156+
})
157+
.catch(() => [])
115158
);
116-
if (!resp.ok) {
117-
console.warn(`Failed to fetch PRs for ${repoName}: ${resp.status} ${resp.statusText}`);
118-
break;
119-
}
120-
const prs: PullRequestItem[] = await resp.json();
121-
if (!Array.isArray(prs) || prs.length === 0) break;
122-
123-
const merged = prs.filter((pr) => Boolean(pr.merged_at));
124-
mergedPRs.push(...merged);
125-
126-
if (prs.length < 100) break;
127-
page++;
128159
}
160+
161+
// Wait for all pages in parallel
162+
const remainingPages = await Promise.all(pagePromises);
163+
remainingPages.forEach(pagePRs => {
164+
if (pagePRs.length > 0) mergedPRs.push(...pagePRs);
165+
});
166+
129167
return mergedPRs;
130168
}, []);
131169

132-
// NEW: Concurrent processing function with controlled concurrency
170+
// Concurrent processing function with controlled concurrency
133171
const processBatch = useCallback(async (
134172
repos: any[],
135173
headers: Record<string, string>
@@ -184,6 +222,17 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps
184222
const fetchAllStats = useCallback(async (signal: AbortSignal) => {
185223
setLoading(true);
186224
setError(null);
225+
226+
// Check cache first
227+
const now = Date.now();
228+
if (cache.data && (now - cache.timestamp) < CACHE_DURATION) {
229+
// console.log('Using cached leaderboard data');
230+
setContributors(cache.data.contributors);
231+
setStats(cache.data.stats);
232+
setLoading(false);
233+
return;
234+
}
235+
187236
if (!token) {
188237
setError("GitHub token not found. Please set customFields.gitToken in docusaurus.config.js.");
189238
setLoading(false);
@@ -196,29 +245,40 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps
196245
Accept: "application/vnd.github.v3+json",
197246
};
198247

199-
// Fetch general organization stats (unchanged)
200-
const orgStats: GitHubOrgStats = await githubService.fetchOrganizationStats(signal);
248+
// Fetch both org stats and repos in parallel
249+
const [orgStats, repos] = await Promise.all([
250+
githubService.fetchOrganizationStats(signal),
251+
fetchAllOrgRepos(headers)
252+
]);
253+
254+
// Set org stats immediately
201255
setGithubStarCount(orgStats.totalStars);
202256
setGithubContributorsCount(orgStats.totalContributors);
203257
setGithubForksCount(orgStats.totalForks);
204258
setGithubReposCount(orgStats.publicRepositories);
205259
setGithubDiscussionsCount(orgStats.discussionsCount);
206260
setLastUpdated(new Date(orgStats.lastUpdated));
207261

208-
// Fetch leaderboard data with concurrent processing
209-
const repos = await fetchAllOrgRepos(headers);
210-
211-
// NEW: Use concurrent processing instead of sequential
262+
// Process leaderboard data with concurrent processing
212263
const { contributorMap, totalMergedPRs } = await processBatch(repos, headers);
213264

214265
const sortedContributors = Array.from(contributorMap.values()).sort(
215266
(a, b) => b.points - a.points || b.prs - a.prs
216267
);
217-
setContributors(sortedContributors);
218-
setStats({
268+
269+
const statsData = {
219270
flooredTotalPRs: totalMergedPRs,
220271
totalContributors: sortedContributors.length,
221272
flooredTotalPoints: sortedContributors.reduce((sum, c) => sum + c.points, 0),
273+
};
274+
275+
setContributors(sortedContributors);
276+
setStats(statsData);
277+
278+
// Cache the results
279+
setCache({
280+
data: { contributors: sortedContributors, stats: statsData },
281+
timestamp: now
222282
});
223283

224284
} catch (err: any) {
@@ -236,12 +296,13 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps
236296
} finally {
237297
setLoading(false);
238298
}
239-
}, [token, fetchAllOrgRepos, processBatch]);
299+
}, [token, fetchAllOrgRepos, processBatch, cache]);
240300

241301
const clearCache = useCallback(() => {
242302
githubService.clearCache();
303+
setCache({ data: null, timestamp: 0 }); // Clear local cache too
243304
const abortController = new AbortController();
244-
fetchAllStats(abortController.signal);
305+
fetchAllStats(abortController.signal);// Refetch data after clearing cache
245306
}, [fetchAllStats]);
246307

247308
useEffect(() => {
@@ -296,16 +357,16 @@ export const useCommunityStatsContext = (): ICommunityStatsContext => {
296357

297358
export const convertStatToText = (num: number): string => {
298359
const hasIntlSupport =
299-
typeof Intl === "object" && Intl && typeof Intl.NumberFormat === "function";
360+
typeof Intl === "object" && Intl && typeof Intl.NumberFormat === "function";
300361

301362
if (!hasIntlSupport) {
302-
return `${(num / 1000).toFixed(1)}k`;
363+
return `${(num / 1000).toFixed(1)}k`; // Fallback for environments without Intl support
303364
}
304365

305366
const formatter = new Intl.NumberFormat("en-US", {
306-
notation: "compact",
367+
notation: "compact",
307368
compactDisplay: "short",
308-
maximumSignificantDigits: 3,
369+
maximumSignificantDigits: 3, // More precise formatting
309370
});
310371
return formatter.format(num);
311372
};

0 commit comments

Comments
 (0)