Skip to content

Commit cc47576

Browse files
committed
fix: store topUsers in batches
1 parent 191dfb9 commit cc47576

2 files changed

Lines changed: 94 additions & 45 deletions

File tree

utils/fetchTopUsersByPullRequests.ts

Lines changed: 82 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,58 +9,98 @@ type UserStat = {
99
export const fetchTopUsersByPullRequests = async (
1010
repo: string
1111
): Promise<UserStat[]> => {
12-
//* Limit to first 300 PRs only
13-
let url = `https://api.github.com/repos/${repo}/pulls?state=closed&per_page=100&page=1`;
1412
const userStats: { [key: string]: { prCount: number; avatarUrl: string } } = {};
15-
let pageCount = 0;
16-
const MAX_PAGES = 3;
13+
const MAX_CONCURRENT_REQUESTS = 3;
14+
const PER_PAGE = 100;
1715

18-
while (url && pageCount < MAX_PAGES) {
19-
const response = await axios.get(url, {
20-
timeout: 5000,
16+
try {
17+
// First, get the total PR count
18+
const initialResponse = await axios.get(`https://api.github.com/repos/${repo}/pulls`, {
19+
params: {
20+
state: 'closed',
21+
per_page: 1
22+
},
2123
headers: {
2224
Authorization: `token ${process.env.GITHUB_TOKEN}`,
2325
}
2426
});
25-
pageCount++;
26-
const prData = response.data;
27-
prData.forEach((pr: any) => {
28-
if (pr.merged_at) {
29-
const username = pr.user.login;
3027

31-
//* Ignore dependabot and nikohoffren PRs
32-
if (
33-
username === "dependabot" ||
34-
username === "dependabot[bot]" ||
35-
username === "nikohoffren"
36-
) {
37-
return;
38-
}
28+
const linkHeader = initialResponse.headers.link;
29+
const lastPageMatch = linkHeader?.match(/page=(\d+)>; rel="last"/);
30+
const totalPages = lastPageMatch ? parseInt(lastPageMatch[1]) : 1;
31+
32+
// Fetch PRs in batches
33+
for (let i = 0; i < totalPages; i += MAX_CONCURRENT_REQUESTS) {
34+
const requests = [];
3935

40-
const avatarUrl = pr.user.avatar_url;
41-
userStats[username] = {
42-
prCount: (userStats[username]?.prCount || 0) + 1,
43-
avatarUrl,
44-
};
36+
// Create batch of concurrent requests
37+
for (let j = 0; j < MAX_CONCURRENT_REQUESTS && (i + j) < totalPages; j++) {
38+
const pageNum = i + j + 1;
39+
requests.push(
40+
axios.get(`https://api.github.com/repos/${repo}/pulls`, {
41+
params: {
42+
state: 'closed',
43+
per_page: PER_PAGE,
44+
page: pageNum
45+
},
46+
headers: {
47+
Authorization: `token ${process.env.GITHUB_TOKEN}`,
48+
},
49+
timeout: 8000
50+
}).catch(error => {
51+
console.error(`Failed to fetch page ${pageNum}:`, error);
52+
return { data: [] };
53+
})
54+
);
4555
}
46-
});
4756

48-
const linkHeader = response.headers.link;
49-
const nextLink = linkHeader
50-
? linkHeader.split(",").find((s: string) => s.includes('rel="next"'))
51-
: null;
52-
url = nextLink ? nextLink.match(/<(.*)>/)?.[1] : null;
53-
}
57+
// Wait for batch to complete
58+
const responses = await Promise.all(requests);
5459

55-
const sortedUsers: UserStat[] = Object.entries(userStats)
56-
.sort(([, a], [, b]) => b.prCount - a.prCount)
57-
.slice(0, 20)
58-
.map(([username, { prCount, avatarUrl }]) => ({
59-
username,
60-
prCount,
61-
avatarUrl,
62-
}));
60+
// Process the responses
61+
responses.forEach(response => {
62+
if (response.data) {
63+
response.data.forEach((pr: any) => {
64+
if (pr.merged_at) {
65+
const username = pr.user.login;
6366

64-
return sortedUsers;
65-
};
67+
// Ignore specific users
68+
if (
69+
username === "dependabot" ||
70+
username === "dependabot[bot]" ||
71+
username === "nikohoffren"
72+
) {
73+
return;
74+
}
75+
76+
const avatarUrl = pr.user.avatar_url;
77+
userStats[username] = {
78+
prCount: (userStats[username]?.prCount || 0) + 1,
79+
avatarUrl,
80+
};
81+
}
82+
});
83+
}
84+
});
85+
86+
// Add a small delay between batches to avoid rate limiting
87+
if (i + MAX_CONCURRENT_REQUESTS < totalPages) {
88+
await new Promise(resolve => setTimeout(resolve, 1000));
89+
}
90+
}
6691

92+
const sortedUsers: UserStat[] = Object.entries(userStats)
93+
.sort(([, a], [, b]) => b.prCount - a.prCount)
94+
.slice(0, 20)
95+
.map(([username, { prCount, avatarUrl }]) => ({
96+
username,
97+
prCount,
98+
avatarUrl,
99+
}));
100+
101+
return sortedUsers;
102+
} catch (error) {
103+
console.error('Error fetching PRs:', error);
104+
throw error;
105+
}
106+
};

utils/fetchTopUsersFromDb.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { connectToDB } from './db';
22

3+
interface UserStat {
4+
login: string;
5+
avatar_url: string;
6+
html_url: string;
7+
contributions: number;
8+
}
9+
310
export async function getTopUsersFromDb() {
411
try {
512
const { db } = await connectToDB();
@@ -11,18 +18,19 @@ export async function getTopUsersFromDb() {
1118
}
1219
}
1320

14-
export async function storeTopUsersInDb(data: any[]) {
21+
export async function storeTopUsersInDb(data: UserStat[]) {
1522
try {
1623
const { db } = await connectToDB();
1724
await db.collection('topUsers').deleteMany({});
1825
await db.collection('topUsers').insertMany(
1926
data.map(user => ({
2027
...user,
21-
timestamp: new Date()
28+
timestamp: new Date(),
29+
lastUpdated: new Date().toISOString()
2230
}))
2331
);
2432
} catch (error) {
25-
console.error('DB Storage Error:', error);
33+
console.error('Error storing top users:', error);
2634
throw error;
2735
}
2836
}
@@ -56,3 +64,4 @@ export async function storeTopThreeUsersInDb(data: any[]) {
5664

5765

5866

67+

0 commit comments

Comments
 (0)