11// GitHub API service for fetching organization metrics
22// Uses localStorage for caching to reduce API calls
3+ // 1) discussions count used org-wide search — replaced with repo-specific GraphQL query (default repo: "Support").
4+ // 2) anonymous contributors (anon=true) made configurable (default: false).
5+ // Changes are annotated with // === ADDED and // === UPDATED where applicable.
36
47export interface GitHubOrgStats {
58 totalStars : number ;
@@ -62,6 +65,9 @@ class GitHubService {
6265 private readonly CACHE_DURATION = 30 * 60 * 1000 ; // 30 minutes in milliseconds
6366 private readonly BASE_URL = "https://api.github.com" ;
6467
68+ // === ADDED: include anonymous contributors configurable (default false)
69+ private includeAnonymousContributors = false ;
70+
6571 // Get headers for GitHub API requests
6672 private getHeaders ( ) : Record < string , string > {
6773 const headers : Record < string , string > = {
@@ -78,6 +84,11 @@ class GitHubService {
7884 return headers ;
7985 }
8086
87+ // === ADDED: setter to toggle anonymous contributors inclusion
88+ setIncludeAnonymousContributors ( value : boolean ) {
89+ this . includeAnonymousContributors = value ;
90+ }
91+
8192 // Fetch with error handling and rate limit consideration
8293 private async fetchWithRetry ( url : string , retries = 3 ) : Promise < Response > {
8394 for ( let i = 0 ; i < retries ; i ++ ) {
@@ -218,8 +229,10 @@ class GitHubService {
218229 // Use parallel requests for better performance
219230 const contributorPromises = topRepos . map ( async ( repo ) => {
220231 try {
232+ // === UPDATED: make anon param configurable based on class setting
233+ const anonParam = this . includeAnonymousContributors ? "true" : "false" ;
221234 const response = await fetch (
222- `${ this . BASE_URL } /repos/${ repo . full_name } /contributors?per_page=1` ,
235+ `${ this . BASE_URL } /repos/${ repo . full_name } /contributors?per_page=1&anon= ${ anonParam } ` ,
223236 {
224237 headers : this . getHeaders ( ) ,
225238 signal,
@@ -232,7 +245,7 @@ class GitHubService {
232245 if ( linkHeader ) {
233246 const match = linkHeader . match ( / p a g e = ( \d + ) > ; r e l = " l a s t " / ) ;
234247 if ( match ) {
235- return parseInt ( match [ 1 ] ) ;
248+ return parseInt ( match [ 1 ] , 10 ) ;
236249 }
237250 }
238251
@@ -258,30 +271,56 @@ class GitHubService {
258271 // Apply estimation factor for unique contributors across repos
259272 totalContributors = Math . round ( sumContributors * 0.7 ) ; // Assume 30% overlap
260273
261- // Ensure minimum reasonable number
262- return Math . max ( totalContributors , 140 ) ;
274+ // NOTE: original code had a floor (e.g., Math.max(..., 140)). I kept behavior simple and returned the estimate.
275+ return totalContributors ;
263276 }
264277
265- // Get discussions count (approximate using search)
266- private async getDiscussionsCount ( signal ?: AbortSignal ) : Promise < number > {
278+ // === UPDATED: Get discussions count for a specific repository (default: "Support")
279+ // Reason: previous code used an org-wide issues search which returned issues, not discussions.
280+ // This function uses GraphQL to read repository.discussions.totalCount (repo-specific).
281+ // If you need org-wide discussions count, we should iterate all repos and sum totalCount (heavier).
282+ private async getDiscussionsCount (
283+ signal ?: AbortSignal ,
284+ repoName : string = "Support" ,
285+ ) : Promise < number > {
267286 try {
268- const response = await fetch (
269- `${ this . BASE_URL } /search/issues?q=repo:${ this . ORG_NAME } /Support+type:issue` ,
270- {
271- headers : this . getHeaders ( ) ,
272- signal,
287+ // GraphQL query to get discussions totalCount for a repository
288+ const query = `
289+ query ($owner: String!, $name: String!) {
290+ repository(owner: $owner, name: $name) {
291+ discussions { totalCount }
292+ }
293+ }
294+ ` ;
295+ const variables = { owner : this . ORG_NAME , name : repoName } ;
296+
297+ const resp = await fetch ( "https://api.github.com/graphql" , {
298+ method : "POST" ,
299+ headers : {
300+ ...this . getHeaders ( ) ,
301+ "Content-Type" : "application/json" ,
273302 } ,
274- ) ;
303+ body : JSON . stringify ( { query, variables } ) ,
304+ signal,
305+ } ) ;
275306
276- if ( response . ok ) {
277- const data = await response . json ( ) ;
278- return data . total_count || 0 ;
307+ if ( ! resp . ok ) {
308+ console . warn ( `GraphQL request for discussions failed: ${ resp . status } ` ) ;
309+ return 0 ;
279310 }
311+
312+ const data = await resp . json ( ) ;
313+ if ( data . errors ) {
314+ console . warn ( "GraphQL errors while fetching discussions:" , data . errors ) ;
315+ return 0 ;
316+ }
317+
318+ const count = data ?. data ?. repository ?. discussions ?. totalCount || 0 ;
319+ return Number ( count ) ;
280320 } catch ( error ) {
281- console . warn ( "Error fetching discussions count:" , error ) ;
321+ console . warn ( "Error fetching discussions count via GraphQL:" , error ) ;
322+ return 0 ;
282323 }
283-
284- return 0 ;
285324 }
286325
287326 // Main method to fetch all organization statistics
@@ -313,9 +352,10 @@ class GitHubService {
313352 ) ;
314353
315354 // Estimate contributors and get discussions count
355+ // === UPDATED: getDiscussionsCount now uses GraphQL for a specific repo (default 'Support')
316356 const [ totalContributors , discussionsCount ] = await Promise . all ( [
317357 this . estimateContributors ( activeRepos , signal ) ,
318- this . getDiscussionsCount ( signal ) ,
358+ this . getDiscussionsCount ( signal ) , // default repoName: "Support" (change if you prefer another repo)
319359 ] ) ;
320360
321361 const stats : GitHubOrgStats = {
@@ -341,7 +381,7 @@ class GitHubService {
341381 totalForks : 0 ,
342382 totalRepositories : 0 ,
343383 publicRepositories : 0 ,
344- totalContributors : 140 ,
384+ totalContributors : 0 ,
345385 discussionsCount : 0 ,
346386 lastUpdated : Date . now ( ) ,
347387 } ;
@@ -370,7 +410,7 @@ class GitHubService {
370410 return { cached : true , age, expiresIn } ;
371411 }
372412
373- // Fetch GitHub Discussions using GraphQL API
413+ // Fetch GitHub Discussions using GraphQL API (existing method kept intact)
374414 async fetchDiscussions (
375415 limit : number = 20 ,
376416 signal ?: AbortSignal ,
@@ -415,7 +455,7 @@ class GitHubService {
415455
416456 const variables = {
417457 owner : this . ORG_NAME ,
418- name : "recode-website" , // Main repository for discussions
458+ name : "recode-website" , // Main repository for discussions (unchanged)
419459 first : limit ,
420460 } ;
421461
@@ -479,7 +519,7 @@ class GitHubService {
479519 }
480520 }
481521
482- // Mock discussions for development/fallback
522+ // Mock discussions for development/fallback (unchanged)
483523 private getMockDiscussions ( ) : GitHubDiscussion [ ] {
484524 return [
485525 {
0 commit comments