From 1e88777fc401684c52cec2f899406431b8c2d127 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Wed, 17 Jun 2026 16:39:28 +0530 Subject: [PATCH 1/4] Update docusaurus.config.ts --- docusaurus.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 185c3910..03757d30 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -283,6 +283,7 @@ const config: Config = { // ✅ Add this customFields object to expose the token to the client-side customFields: { + backendApiUrl: process.env.BACKEND_API_URL || "http://localhost:5000", gitToken: process.env.DOCUSAURUS_GIT_TOKEN, clerkPublishableKey: process.env.VITE_CLERK_PUBLISHABLE_KEY || "", // Shopify credentials for merch store From dc2b11818d3cb9bcdffdbcf025d60e44fcb1d230 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Wed, 17 Jun 2026 16:39:31 +0530 Subject: [PATCH 2/4] Update statsProvider.tsx --- src/lib/statsProvider.tsx | 224 ++++++-------------------------------- 1 file changed, 31 insertions(+), 193 deletions(-) diff --git a/src/lib/statsProvider.tsx b/src/lib/statsProvider.tsx index 923c16c5..b16e0d1a 100644 --- a/src/lib/statsProvider.tsx +++ b/src/lib/statsProvider.tsx @@ -164,6 +164,7 @@ export function CommunityStatsProvider({ siteConfig: { customFields }, } = useDocusaurusContext(); const token = customFields?.gitToken || ""; + const backendApiUrl = (customFields?.backendApiUrl as string) || "http://localhost:5000"; const [loading, setLoading] = useState(false); // Start with false to avoid hourglass const [error, setError] = useState(null); @@ -251,164 +252,6 @@ export function CommunityStatsProvider({ setCurrentTimeFilter(filter); }, []); - const fetchAllOrgRepos = useCallback( - async (headers: Record) => { - const repos: any[] = []; - let page = 1; - while (true) { - const resp = await fetch( - `https://api.github.com/orgs/${GITHUB_ORG}/repos?type=public&per_page=100&page=${page}`, - { - headers, - }, - ); - if (!resp.ok) { - throw new Error( - `Failed to fetch org repos: ${resp.status} ${resp.statusText}`, - ); - } - const data = await resp.json(); - repos.push(...data); - if (!Array.isArray(data) || data.length < 100) break; - page++; - } - return repos; - }, - [], - ); - - const fetchMergedPRsForRepo = useCallback( - async (repoName: string, headers: Record) => { - const mergedPRs: PullRequestItem[] = []; - - // First, get the first page to estimate total pages - const firstResp = await fetch( - `https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=1`, - { headers }, - ); - - if (!firstResp.ok) { - console.warn( - `Failed to fetch PRs for ${repoName}: ${firstResp.status} ${firstResp.statusText}`, - ); - return []; - } - - const firstPRs: PullRequestItem[] = await firstResp.json(); - if (!Array.isArray(firstPRs) || firstPRs.length === 0) return []; - - const firstPageMerged = firstPRs.filter((pr) => Boolean(pr.merged_at)); - mergedPRs.push(...firstPageMerged); - - // If we got less than 100, that's all there is - if (firstPRs.length < 100) return mergedPRs; - - // Create parallel requests for remaining pages - const pagePromises: Promise[] = []; - const maxPages = Math.min(MAX_PAGES_PER_REPO, 10); - - for (let i = 2; i <= maxPages; i++) { - pagePromises.push( - fetch( - `https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=${i}`, - { headers }, - ) - .then(async (resp) => { - if (!resp.ok) return []; - const prs: PullRequestItem[] = await resp.json(); - if (!Array.isArray(prs)) return []; - return prs.filter((pr) => Boolean(pr.merged_at)); - }) - .catch(() => []), - ); - } - - // Wait for all pages in parallel - const remainingPages = await Promise.all(pagePromises); - remainingPages.forEach((pagePRs) => { - if (pagePRs.length > 0) mergedPRs.push(...pagePRs); - }); - - return mergedPRs; - }, - [], - ); - - // Enhanced processing function that stores only valid PRs with points - const processBatch = useCallback( - async ( - repos: any[], - headers: Record, - ): Promise<{ - contributorMap: Map; - totalMergedPRs: number; - }> => { - const contributorMap = new Map(); - let totalMergedPRs = 0; - - // Process repos in batches to control concurrency - for (let i = 0; i < repos.length; i += MAX_CONCURRENT_REQUESTS) { - const batch = repos.slice(i, i + MAX_CONCURRENT_REQUESTS); - - const promises = batch.map(async (repo) => { - if (repo.archived) return { mergedPRs: [], repoName: repo.name }; - - try { - const mergedPRs = await fetchMergedPRsForRepo(repo.name, headers); - return { mergedPRs, repoName: repo.name }; - } catch (error) { - console.warn(`Skipping repo ${repo.name} due to error:`, error); - return { mergedPRs: [], repoName: repo.name }; - } - }); - - // Wait for current batch to complete - const results = await Promise.all(promises); - - // Process results from this batch - results.forEach(({ mergedPRs, repoName }) => { - mergedPRs.forEach((pr) => { - // Calculate points for this PR based on labels - const prPoints = calculatePointsForPR(pr.labels); - - // ONLY store PRs that have points (i.e., have "recode" label and a level label) - if (prPoints > 0) { - totalMergedPRs++; - - const username = pr.user.login; - if (!contributorMap.has(username)) { - contributorMap.set(username, { - username, - avatar: pr.user.avatar_url, - profile: pr.user.html_url, - points: 0, // Will be calculated later based on filter - prs: 0, // Will be calculated later based on filter - allPRDetails: [], // Store only valid PRs here - }); - } - const contributor = contributorMap.get(username)!; - - // Add detailed PR information only if it has all required fields - if (pr.title && pr.html_url && pr.merged_at && pr.number) { - contributor.allPRDetails.push({ - title: pr.title, - url: pr.html_url, - mergedAt: pr.merged_at, - repoName, - number: pr.number, - points: prPoints, - }); - } - } - }); - }); - } - - return { contributorMap, totalMergedPRs }; - }, - [fetchMergedPRsForRepo], - ); - const fetchAllStats = useCallback( async (signal: AbortSignal) => { // Check cache first and load it immediately without showing loading state @@ -433,41 +276,36 @@ export function CommunityStatsProvider({ setError(null); - if (!token) { - setError( - "GitHub token not found. Please set customFields.gitToken in docusaurus.config.js.", - ); - setLoading(false); - return; - } - try { - const headers: Record = { - Authorization: `token ${token}`, - Accept: "application/vnd.github.v3+json", - }; - - // Fetch both org stats and repos in parallel - const [orgStats, repos] = await Promise.all([ - githubService.fetchOrganizationStats(signal), - fetchAllOrgRepos(headers), + const [leaderboardResp, statsResp] = await Promise.all([ + fetch(`${backendApiUrl}/api/leaderboard`, { signal }), + fetch(`${backendApiUrl}/api/stats`, { signal }) ]); - // Set org stats immediately - setGithubStarCount(orgStats.totalStars); - setGithubContributorsCount(orgStats.totalContributors); - setGithubForksCount(orgStats.totalForks); - setGithubReposCount(orgStats.publicRepositories); - setGithubDiscussionsCount(orgStats.discussionsCount); - setLastUpdated(new Date(orgStats.lastUpdated)); - - // Process leaderboard data with concurrent processing - const { contributorMap, totalMergedPRs } = await processBatch( - repos, - headers, - ); + if (!leaderboardResp.ok || !statsResp.ok) { + throw new Error("Failed to fetch leaderboard data from backend server"); + } - const contributorsArray = Array.from(contributorMap.values()); + const leaderboardData = await leaderboardResp.json(); + const statsData = await statsResp.json(); + + // Set org stats immediately + setGithubStarCount(statsData.totalStars); + setGithubContributorsCount(statsData.totalContributors); + setGithubForksCount(statsData.totalForks); + setGithubReposCount(statsData.publicRepositories); + setGithubDiscussionsCount(statsData.discussionsCount); + setLastUpdated(new Date(statsData.lastUpdated)); + + // Format to FullContributor (which matches contributor mapping) + const contributorsArray: FullContributor[] = (leaderboardData.contributors || []).map((c: any) => ({ + username: c.username, + avatar: c.avatar, + profile: c.profile, + points: c.points, + prs: c.prs, + allPRDetails: c.prDetails || [] // Backend stores complete list + })); setAllContributors(contributorsArray); @@ -475,15 +313,15 @@ export function CommunityStatsProvider({ setCache({ data: { contributors: contributorsArray, - rawStats: { totalPRs: totalMergedPRs }, + rawStats: { totalPRs: statsData.totalContributors }, }, timestamp: now, }); } catch (err: any) { if (err.name !== "AbortError") { - console.error("Error fetching GitHub organization stats:", err); + console.error("Error fetching stats from backend:", err); setError( - err instanceof Error ? err.message : "Failed to fetch GitHub stats", + err instanceof Error ? err.message : "Failed to fetch stats from backend", ); // Set fallback values on error @@ -497,7 +335,7 @@ export function CommunityStatsProvider({ setLoading(false); } }, - [token, fetchAllOrgRepos, processBatch, cache], + [backendApiUrl, cache], ); const clearCache = useCallback(() => { From bff6d8fd8a96a416afca3e37b773219de6681f62 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Wed, 17 Jun 2026 16:39:34 +0530 Subject: [PATCH 3/4] Update githubService.ts --- src/services/githubService.ts | 232 ---------------------------------- 1 file changed, 232 deletions(-) diff --git a/src/services/githubService.ts b/src/services/githubService.ts index 9b9aece7..ba66c018 100644 --- a/src/services/githubService.ts +++ b/src/services/githubService.ts @@ -168,239 +168,7 @@ class GitHubService { } } - // Fetch organization basic info - private async fetchOrganizationInfo( - signal?: AbortSignal, - ): Promise { - const response = await fetch(`${this.BASE_URL}/orgs/${this.ORG_NAME}`, { - headers: this.getHeaders(), - signal, - }); - - if (!response.ok) { - throw new Error(`Failed to fetch organization info: ${response.status}`); - } - - return response.json(); - } - - // Fetch all public repositories for the organization - private async fetchAllRepositories( - signal?: AbortSignal, - ): Promise { - const repositories: GitHubRepository[] = []; - let page = 1; - const perPage = 100; - - while (true) { - const response = await fetch( - `${this.BASE_URL}/orgs/${this.ORG_NAME}/repos?type=public&per_page=${perPage}&page=${page}&sort=updated`, - { - headers: this.getHeaders(), - signal, - }, - ); - - if (!response.ok) { - if (response.status === 403) { - console.warn( - "GitHub API rate limit exceeded while fetching repositories", - ); - throw new Error("GitHub API rate limit exceeded"); - } - throw new Error(`Failed to fetch repositories: ${response.status}`); - } - - const repos: GitHubRepository[] = await response.json(); - - if (repos.length === 0) break; - - repositories.push(...repos); - - if (repos.length < perPage) break; - - page++; - } - - return repositories; - } - - // Estimate contributors count (GitHub API doesn't provide org-wide contributor count) - private async estimateContributors( - repositories: GitHubRepository[], - signal?: AbortSignal, - ): Promise { - // For performance, we'll sample top repositories by stars/activity - const topRepos = repositories - .filter((repo) => !repo.archived && repo.stargazers_count > 0) - .sort((a, b) => b.stargazers_count - a.stargazers_count) - .slice(0, 10); // Sample top 10 repositories - - let totalContributors = 0; - - // Use parallel requests for better performance - const contributorPromises = topRepos.map(async (repo) => { - try { - // === UPDATED: make anon param configurable based on class setting - const anonParam = this.includeAnonymousContributors ? "true" : "false"; - const response = await fetch( - `${this.BASE_URL}/repos/${repo.full_name}/contributors?per_page=1&anon=${anonParam}`, - { - headers: this.getHeaders(), - signal, - }, - ); - - if (response.ok) { - // Get total count from Link header if available - const linkHeader = response.headers.get("Link"); - if (linkHeader) { - const match = linkHeader.match(/page=(\d+)>; rel="last"/); - if (match) { - return parseInt(match[1], 10); - } - } - - // Fallback: count actual contributors - const contributors = await response.json(); - return Array.isArray(contributors) ? contributors.length : 0; - } - return 0; - } catch (error) { - console.warn(`Error fetching contributors for ${repo.name}:`, error); - return 0; - } - }); - - const contributorCounts = await Promise.all(contributorPromises); - - // Estimate total unique contributors (with some overlap factor) - const sumContributors = contributorCounts.reduce( - (sum, count) => sum + count, - 0, - ); - // Apply estimation factor for unique contributors across repos - totalContributors = Math.round(sumContributors * 0.7); // Assume 30% overlap - - // NOTE: original code had a floor (e.g., Math.max(..., 140)). I kept behavior simple and returned the estimate. - return totalContributors; - } - - // === UPDATED: Get discussions count for a specific repository (default: "Support") - // Reason: previous code used an org-wide issues search which returned issues, not discussions. - // This function uses GraphQL to read repository.discussions.totalCount (repo-specific). - // If you need org-wide discussions count, we should iterate all repos and sum totalCount (heavier). - private async getDiscussionsCount( - signal?: AbortSignal, - repoName: string = "Support", - ): Promise { - try { - // GraphQL query to get discussions totalCount for a repository - const query = ` - query ($owner: String!, $name: String!) { - repository(owner: $owner, name: $name) { - discussions { totalCount } - } - } - `; - const variables = { owner: this.ORG_NAME, name: repoName }; - - const resp = await fetch("https://api.github.com/graphql", { - method: "POST", - headers: { - ...this.getHeaders(), - "Content-Type": "application/json", - }, - body: JSON.stringify({ query, variables }), - signal, - }); - - if (!resp.ok) { - console.warn(`GraphQL request for discussions failed: ${resp.status}`); - return 0; - } - - const data = await resp.json(); - if (data.errors) { - console.warn("GraphQL errors while fetching discussions:", data.errors); - return 0; - } - - const count = data?.data?.repository?.discussions?.totalCount || 0; - return Number(count); - } catch (error) { - console.warn("Error fetching discussions count via GraphQL:", error); - return 0; - } - } - - // Main method to fetch all organization statistics - async fetchOrganizationStats(signal?: AbortSignal): Promise { - // Try to get cached data first - const cached = this.getCachedData(); - if (cached) { - return cached; - } - - try { - // Fetch organization info and repositories in parallel - const [orgInfo, repositories] = await Promise.all([ - this.fetchOrganizationInfo(signal), - this.fetchAllRepositories(signal), - ]); - - // Filter out archived repositories for active stats - const activeRepos = repositories.filter((repo) => !repo.archived); - - // Calculate totals - const totalStars = repositories.reduce( - (sum, repo) => sum + repo.stargazers_count, - 0, - ); - const totalForks = repositories.reduce( - (sum, repo) => sum + repo.forks_count, - 0, - ); - - // Estimate contributors and get discussions count - // === UPDATED: getDiscussionsCount now uses GraphQL for a specific repo (default 'Support') - const [totalContributors, discussionsCount] = await Promise.all([ - this.estimateContributors(activeRepos, signal), - this.getDiscussionsCount(signal), // default repoName: "Support" (change if you prefer another repo) - ]); - - const stats: GitHubOrgStats = { - totalStars, - totalForks, - totalRepositories: repositories.length, - publicRepositories: activeRepos.length, - totalContributors, - discussionsCount, - lastUpdated: Date.now(), - }; - - // Cache the results - this.setCachedData(stats); - - return stats; - } catch (error) { - console.error("Error fetching GitHub organization stats:", error); - - // Return fallback data if API fails - const fallbackStats: GitHubOrgStats = { - totalStars: 0, - totalForks: 0, - totalRepositories: 0, - publicRepositories: 0, - totalContributors: 0, - discussionsCount: 0, - lastUpdated: Date.now(), - }; - - return fallbackStats; - } - } // Clear cache (useful for manual refresh) clearCache(): void { From a2f5b2d3cbfd0368f90f9210ef2f07914b501956 Mon Sep 17 00:00:00 2001 From: Sanjay Viswanathan Date: Thu, 18 Jun 2026 12:14:07 +1000 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docusaurus.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 03757d30..09b01040 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -281,7 +281,7 @@ const config: Config = { ], ], - // ✅ Add this customFields object to expose the token to the client-side + // Custom fields exposed to client-side code (browser-visible at build time) customFields: { backendApiUrl: process.env.BACKEND_API_URL || "http://localhost:5000", gitToken: process.env.DOCUSAURUS_GIT_TOKEN,