@@ -37,7 +37,7 @@ function allIncluded(outputTarget = 'email') {
37
37
let issue_opened_button =
38
38
'<div style="vertical-align:middle;display: inline-block;padding: 0px 4px;font-size:9px;font-weight: 600;color: #fff;text-align: center;background-color: #2cbe4e;border-radius: 3px;line-height: 12px;margin-bottom: 2px;" class="State State--green">open</div>' ;
39
39
40
- const DEBUG = true ; // Set to false to disable debug logs
40
+ const DEBUG = false ; // Set to false to disable debug logs
41
41
42
42
function log ( ...args ) {
43
43
if ( DEBUG ) {
@@ -224,20 +224,17 @@ function allIncluded(outputTarget = 'email') {
224
224
chrome . storage . local . get ( 'githubCache' , ( result ) => {
225
225
const cache = result . githubCache ;
226
226
if ( ! cache ) {
227
- log ( 'No cache found in storage' ) ;
227
+
228
228
resolve ( false ) ;
229
229
return ;
230
230
}
231
231
const isCacheExpired = ( Date . now ( ) - cache . timestamp ) > githubCache . ttl ;
232
232
if ( isCacheExpired ) {
233
- log ( 'Cached data is expired' ) ;
233
+
234
234
resolve ( false ) ;
235
235
return ;
236
236
}
237
- log ( 'Found valid cache:' , {
238
- cacheKey : cache . cacheKey ,
239
- age : `${ ( ( Date . now ( ) - cache . timestamp ) / 1000 / 60 ) . toFixed ( 1 ) } minutes` ,
240
- } ) ;
237
+
241
238
242
239
githubCache . data = cache . data ;
243
240
githubCache . cacheKey = cache . cacheKey ;
@@ -256,47 +253,69 @@ function allIncluded(outputTarget = 'email') {
256
253
async function fetchGithubData ( ) {
257
254
const cacheKey = `${ githubUsername } -${ startingDate } -${ endingDate } ` ;
258
255
259
- if ( githubCache . fetching || ( githubCache . cacheKey === cacheKey && githubCache . data ) ) {
260
- return ;
261
- }
262
-
263
- log ( 'Fetching Github data:' , {
256
+ log ( 'Fetch request:' , {
264
257
username : githubUsername ,
265
- dateRange : `${ startingDate } to ${ endingDate } `
258
+ startDate : startingDate ,
259
+ endDate : endingDate ,
260
+ cacheKey : cacheKey
266
261
} ) ;
267
262
263
+ if ( githubCache . fetching ) {
264
+ log ( 'Fetch already in progress, queuing request' ) ;
265
+ return new Promise ( ( resolve , reject ) => {
266
+ githubCache . queue . push ( { resolve, reject } ) ;
267
+ } ) ;
268
+ }
269
+
270
+ if ( githubCache . cacheKey === cacheKey && githubCache . data ) {
271
+ log ( 'Using cached data:' , {
272
+ age : `${ ( ( Date . now ( ) - githubCache . timestamp ) / 1000 / 60 ) . toFixed ( 1 ) } minutes` ,
273
+ dataTypes : Object . keys ( githubCache . data )
274
+ } ) ;
275
+ return ;
276
+ }
277
+
268
278
// Check if we need to load from storage
269
279
if ( ! githubCache . data && ! githubCache . fetching ) {
270
- await loadFromStorage ( ) ;
280
+ const loaded = await loadFromStorage ( ) ;
281
+ log ( 'Storage load result:' , { loaded, hasCachedData : ! ! githubCache . data } ) ;
271
282
}
272
283
273
284
const now = Date . now ( ) ;
274
285
const isCacheFresh = ( now - githubCache . timestamp ) < githubCache . ttl ;
275
286
const isCacheKeyMatch = githubCache . cacheKey === cacheKey ;
276
287
288
+ log ( 'Cache status:' , {
289
+ isFresh : isCacheFresh ,
290
+ keyMatch : isCacheKeyMatch ,
291
+ age : githubCache . timestamp ? `${ ( ( now - githubCache . timestamp ) / 1000 / 60 ) . toFixed ( 1 ) } minutes` : 'no cache'
292
+ } ) ;
293
+
277
294
if ( githubCache . data && isCacheFresh && isCacheKeyMatch ) {
295
+ log ( 'Using existing cache' ) ;
278
296
processGithubData ( githubCache . data ) ;
279
297
return Promise . resolve ( ) ;
280
298
}
281
299
282
300
if ( ! isCacheKeyMatch ) {
301
+ log ( 'Cache key mismatch, clearing data' ) ;
283
302
githubCache . data = null ;
284
303
}
285
304
286
- // if fetching is in progress, queue the calls and return a promise resolved when done
287
- if ( githubCache . fetching ) {
288
- return new Promise ( ( resolve , reject ) => {
289
- githubCache . queue . push ( { resolve, reject } ) ;
290
- } ) ;
291
- }
292
-
293
305
githubCache . fetching = true ;
294
306
githubCache . cacheKey = cacheKey ;
295
307
296
308
let issueUrl = `https://api.github.com/search/issues?q=author%3A${ githubUsername } +org%3Afossasia+created%3A${ startingDate } ..${ endingDate } &per_page=100` ;
297
309
let prUrl = `https://api.github.com/search/issues?q=commenter%3A${ githubUsername } +org%3Afossasia+updated%3A${ startingDate } ..${ endingDate } &per_page=100` ;
298
310
let userUrl = `https://api.github.com/users/${ githubUsername } ` ;
299
- let allPrsUrl = `https://api.github.com/search/issues?q=type:pr+author:${ githubUsername } +org:fossasia&per_page=100` ;
311
+ let allPrsUrl = `https://api.github.com/search/issues?q=type:pr+author:${ githubUsername } +org:fossasia+state:open&per_page=100` ;
312
+
313
+ log ( 'Fetching data from URLs:' , {
314
+ issues : issueUrl ,
315
+ prs : prUrl ,
316
+ user : userUrl ,
317
+ allPrs : allPrsUrl
318
+ } ) ;
300
319
301
320
try {
302
321
// throttling 500ms to avoid burst
@@ -325,93 +344,154 @@ function allIncluded(outputTarget = 'email') {
325
344
326
345
// Fetch commits for each open PR with better rate limiting and caching
327
346
const fetchCommitsWithRetry = async ( owner , project , pr_number , retryCount = 3 ) => {
328
- const commitsUrl = `https://api.github.com/repos/${ owner } /${ project } /pulls/${ pr_number } /commits` ;
347
+ const fetchCommitsPage = async ( pageUrl ) => {
348
+ const response = await fetch ( pageUrl ) ;
349
+ if ( ! response . ok ) {
350
+ throw new Error ( `HTTP ${ response . status } ` ) ;
351
+ }
352
+
353
+ // Get pagination links from header
354
+ const linkHeader = response . headers . get ( 'Link' ) ;
355
+ let nextPage = null ;
356
+ if ( linkHeader ) {
357
+ const links = linkHeader . split ( ',' ) ;
358
+ const nextLink = links . find ( link => link . includes ( 'rel="next"' ) ) ;
359
+ if ( nextLink ) {
360
+ const match = nextLink . match ( / < ( [ ^ > ] + ) > / ) ;
361
+ if ( match ) nextPage = match [ 1 ] ;
362
+ }
363
+ }
364
+
365
+ const commits = await response . json ( ) ;
366
+ return { commits, nextPage } ;
367
+ } ;
368
+
369
+ const getAllCommits = async ( initialUrl ) => {
370
+ let url = initialUrl ;
371
+ let allCommits = [ ] ;
372
+
373
+ while ( url ) {
374
+ log ( `Fetching commits page from ${ url } ` ) ;
375
+ const { commits, nextPage } = await fetchCommitsPage ( url ) ;
376
+ allCommits = allCommits . concat ( commits ) ;
377
+ url = nextPage ;
378
+
379
+ if ( nextPage ) {
380
+ // Add delay before fetching next page
381
+ await new Promise ( res => setTimeout ( res , 1000 ) ) ;
382
+ }
383
+ }
384
+
385
+ return allCommits ;
386
+ } ;
387
+
388
+ const commitsUrl = `https://api.github.com/repos/${ owner } /${ project } /pulls/${ pr_number } /commits?per_page=100` ;
389
+ log ( `Fetching all commits for PR #${ pr_number } from ${ commitsUrl } ` ) ;
329
390
330
391
// Check if we have an error cached for this URL
331
392
const errorKey = `${ owner } /${ project } /${ pr_number } ` ;
332
393
const cachedError = githubCache . errors [ errorKey ] ;
333
394
if ( cachedError && ( Date . now ( ) - cachedError . timestamp ) < githubCache . errorTTL ) {
334
- log ( `Skipping ${ errorKey } due to recent error` ) ;
395
+ log ( `Skipping ${ errorKey } due to recent error:` , cachedError ) ;
335
396
return null ;
336
397
}
337
398
338
399
for ( let i = 0 ; i < retryCount ; i ++ ) {
339
400
try {
340
401
// Add exponential backoff between retries
341
402
if ( i > 0 ) {
342
- await new Promise ( res => setTimeout ( res , Math . pow ( 2 , i ) * 1000 ) ) ;
343
- }
344
-
345
- const commitsRes = await fetch ( commitsUrl ) ;
346
- if ( commitsRes . status === 404 ) {
347
- // Cache 404 errors to avoid retrying
348
- githubCache . errors [ errorKey ] = { timestamp : Date . now ( ) , status : 404 } ;
349
- return null ;
403
+ const delay = Math . pow ( 2 , i ) * 1000 ;
404
+ log ( `Retry ${ i + 1 } /${ retryCount } for PR #${ pr_number } , waiting ${ delay } ms` ) ;
405
+ await new Promise ( res => setTimeout ( res , delay ) ) ;
350
406
}
351
407
352
- if ( commitsRes . status === 403 ) {
353
- // Rate limit hit - wait longer
354
- const resetTime = commitsRes . headers . get ( 'X-RateLimit-Reset' ) ;
355
- if ( resetTime ) {
356
- const waitTime = ( parseInt ( resetTime ) * 1000 ) - Date . now ( ) ;
357
- if ( waitTime > 0 ) {
358
- await new Promise ( res => setTimeout ( res , waitTime ) ) ;
359
- }
360
- }
361
- continue ;
362
- }
363
-
364
- if ( ! commitsRes . ok ) {
365
- throw new Error ( `HTTP ${ commitsRes . status } ` ) ;
366
- }
408
+ const commits = await getAllCommits ( commitsUrl ) ;
409
+ log ( `Fetched total ${ commits . length } commits for PR #${ pr_number } in ${ owner } /${ project } ` ) ;
410
+
411
+ // Log commit details for debugging
412
+ commits . forEach ( commit => {
413
+ log ( `Commit in PR #${ pr_number } :` , {
414
+ sha : commit . sha . substring ( 0 , 7 ) ,
415
+ author : commit . author ?. login ,
416
+ date : commit . commit . author . date ,
417
+ message : commit . commit . message . split ( '\n' ) [ 0 ]
418
+ } ) ;
419
+ } ) ;
367
420
368
- const commits = await commitsRes . json ( ) ;
369
- log ( `Fetched ${ commits . length } commits for PR #${ pr_number } in ${ owner } /${ project } ` ) ;
370
421
return commits ;
371
422
372
423
} catch ( err ) {
424
+ logError ( `Error fetching commits for PR #${ pr_number } (attempt ${ i + 1 } /${ retryCount } ):` , err ) ;
373
425
if ( i === retryCount - 1 ) {
374
- // Cache error on final retry
375
426
githubCache . errors [ errorKey ] = { timestamp : Date . now ( ) , error : err . message } ;
376
- logError ( `Failed to fetch commits for PR #${ pr_number } after ${ retryCount } retries:` , err ) ;
377
427
return null ;
378
428
}
379
429
}
380
430
}
381
431
return null ;
382
432
} ;
383
433
384
- // Process PRs in batches to avoid rate limiting
385
- const batchSize = 3 ;
434
+ // Process PRs in smaller batches with longer delays
435
+ const batchSize = 2 ;
386
436
const prCommits = [ ] ;
387
437
388
438
for ( let i = 0 ; i < openPrs . length ; i += batchSize ) {
389
439
const batch = openPrs . slice ( i , i + batchSize ) ;
440
+ log ( `Processing batch ${ Math . floor ( i / batchSize ) + 1 } /${ Math . ceil ( openPrs . length / batchSize ) } ` ) ;
441
+
390
442
const batchResults = await Promise . all ( batch . map ( async pr => {
391
443
const repository_url = pr . repository_url ;
392
444
const [ owner , project ] = repository_url . split ( '/' ) . slice ( - 2 ) ;
393
445
394
- // Add delay between PR commit fetches
395
- await new Promise ( res => setTimeout ( res , 1000 ) ) ;
446
+ // Increased delay between PR commit fetches
447
+ await new Promise ( res => setTimeout ( res , 2000 ) ) ;
396
448
397
449
const commits = await fetchCommitsWithRetry ( owner , project , pr . number ) ;
398
450
if ( ! commits ) return null ;
399
451
400
- const filteredCommits = commits . filter ( commit =>
401
- commit . author ?. login === githubUsername &&
402
- new Date ( commit . commit . author . date ) >= new Date ( startingDate ) &&
403
- new Date ( commit . commit . author . date ) <= new Date ( endingDate )
404
- ) ;
452
+ const filteredCommits = commits . filter ( commit => {
453
+ const isAuthor = commit . author ?. login === githubUsername ;
454
+ const commitDate = new Date ( commit . commit . author . date ) ;
455
+
456
+ // Create start and end date objects
457
+ const startDateObj = new Date ( startingDate ) ;
458
+ const endDateObj = new Date ( endingDate ) ;
459
+ // Set end date to end of day (23:59:59.999)
460
+ endDateObj . setHours ( 23 , 59 , 59 , 999 ) ;
461
+
462
+ const isInDateRange = commitDate >= startDateObj &&
463
+ commitDate <= endDateObj ;
464
+
465
+ log ( `Filtering commit ${ commit . sha . substring ( 0 , 7 ) } :` , {
466
+ isAuthor,
467
+ author : commit . author ?. login ,
468
+ commitDate : commit . commit . author . date ,
469
+ inRange : isInDateRange ,
470
+ startDate : startDateObj . toISOString ( ) ,
471
+ endDate : endDateObj . toISOString ( )
472
+ } ) ;
405
473
406
- if ( filteredCommits . length === 0 ) return null ;
474
+ return isAuthor && isInDateRange ;
475
+ } ) ;
407
476
477
+ if ( filteredCommits . length === 0 ) {
478
+ log ( `No matching commits found for PR #${ pr . number } ` ) ;
479
+ return null ;
480
+ }
481
+
482
+ log ( `Found ${ filteredCommits . length } matching commits in PR #${ pr . number } ` ) ;
408
483
return {
409
484
pr,
410
485
commits : filteredCommits
411
486
} ;
412
487
} ) ) ;
413
488
414
489
prCommits . push ( ...batchResults . filter ( Boolean ) ) ;
490
+
491
+ // Add delay between batches
492
+ if ( i + batchSize < openPrs . length ) {
493
+ await new Promise ( res => setTimeout ( res , 3000 ) ) ;
494
+ }
415
495
}
416
496
417
497
// Filter out null results and empty commits
@@ -481,7 +561,14 @@ function allIncluded(outputTarget = 'email') {
481
561
verifyCacheStatus ( ) ;
482
562
483
563
async function forceGithubDataRefresh ( ) {
564
+ log ( 'Starting force refresh...' ) ;
484
565
const oldCacheKey = githubCache . cacheKey ;
566
+ log ( 'Old cache state:' , {
567
+ key : oldCacheKey ,
568
+ hasData : ! ! githubCache . data ,
569
+ timestamp : githubCache . timestamp
570
+ } ) ;
571
+
485
572
githubCache = {
486
573
data : null ,
487
574
cacheKey : oldCacheKey ,
@@ -494,21 +581,25 @@ function allIncluded(outputTarget = 'email') {
494
581
subject : null
495
582
} ;
496
583
584
+ log ( 'Cache reset complete' ) ;
585
+
497
586
await new Promise ( resolve => {
498
- chrome . storage . local . remove ( 'githubCache' , resolve ) ;
587
+ chrome . storage . local . remove ( 'githubCache' , ( ) => {
588
+ log ( 'Storage cache cleared' ) ;
589
+ resolve ( ) ;
590
+ } ) ;
499
591
} ) ;
500
592
501
593
try {
594
+ log ( 'Starting fresh data fetch...' ) ;
502
595
await fetchGithubData ( ) ;
596
+ log ( 'Force refresh completed successfully' ) ;
503
597
return { success : true , timestamp : Date . now ( ) } ;
504
598
} catch ( err ) {
505
599
logError ( 'Force refresh failed:' , err ) ;
506
600
throw err ;
507
601
}
508
602
}
509
- if ( typeof window !== 'undefined' ) {
510
- window . forceGithubDataRefresh = forceGithubDataRefresh ;
511
- }
512
603
513
604
function processGithubData ( data ) {
514
605
githubIssuesData = data . githubIssuesData ;
0 commit comments