1- // src/lib/statsProvider.tsx
21
32/** @jsxImportSource react */
43import React , {
@@ -66,7 +65,8 @@ interface CommunityStatsProviderProps {
6665
6766const GITHUB_ORG = "recodehive" ;
6867const 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
7171export 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
297358export 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