Skip to content

Commit af97e57

Browse files
fix: fetch all repos for for the stats card (#2100)
* fetch all stars * stop fetching when there are repos with zero stars * remove not needed parameters from the query * add docstring * removed not needed mock * style: update formatting Co-authored-by: rickstaa <[email protected]>
1 parent acbc03d commit af97e57

File tree

3 files changed

+175
-18
lines changed

3 files changed

+175
-18
lines changed

src/fetchers/stats-fetcher.js

+68-10
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ const fetcher = (variables, token) => {
2929
totalCommitContributions
3030
restrictedContributionsCount
3131
}
32-
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
32+
repositoriesContributedTo(contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
3333
totalCount
3434
}
35-
pullRequests(first: 1) {
35+
pullRequests {
3636
totalCount
3737
}
3838
openIssues: issues(states: OPEN) {
@@ -44,14 +44,41 @@ const fetcher = (variables, token) => {
4444
followers {
4545
totalCount
4646
}
47-
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
47+
repositories(ownerAffiliations: OWNER) {
4848
totalCount
49+
}
50+
}
51+
}
52+
`,
53+
variables,
54+
},
55+
{
56+
Authorization: `bearer ${token}`,
57+
},
58+
);
59+
};
60+
61+
/**
62+
* @param {import('axios').AxiosRequestHeaders} variables
63+
* @param {string} token
64+
*/
65+
const repositoriesFetcher = (variables, token) => {
66+
return request(
67+
{
68+
query: `
69+
query userInfo($login: String!, $after: String) {
70+
user(login: $login) {
71+
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
4972
nodes {
5073
name
5174
stargazers {
5275
totalCount
5376
}
5477
}
78+
pageInfo {
79+
hasNextPage
80+
endCursor
81+
}
5582
}
5683
}
5784
}
@@ -99,6 +126,43 @@ const totalCommitsFetcher = async (username) => {
99126
return 0;
100127
};
101128

129+
/**
130+
* Fetch all the stars for all the repositories of a given username
131+
* @param {string} username
132+
* @param {array} repoToHide
133+
*/
134+
const totalStarsFetcher = async (username, repoToHide) => {
135+
let nodes = [];
136+
let hasNextPage = true;
137+
let endCursor = null;
138+
while (hasNextPage) {
139+
const variables = { login: username, first: 100, after: endCursor };
140+
let res = await retryer(repositoriesFetcher, variables);
141+
142+
if (res.data.errors) {
143+
logger.error(res.data.errors);
144+
throw new CustomError(
145+
res.data.errors[0].message || "Could not fetch user",
146+
CustomError.USER_NOT_FOUND,
147+
);
148+
}
149+
150+
const allNodes = res.data.data.user.repositories.nodes;
151+
const nodesWithStars = allNodes.filter(
152+
(node) => node.stargazers.totalCount !== 0,
153+
);
154+
nodes.push(...nodesWithStars);
155+
hasNextPage =
156+
allNodes.length === nodesWithStars.length &&
157+
res.data.data.user.repositories.pageInfo.hasNextPage;
158+
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
159+
}
160+
161+
return nodes
162+
.filter((data) => !repoToHide[data.name])
163+
.reduce((prev, curr) => prev + curr.stargazers.totalCount, 0);
164+
};
165+
102166
/**
103167
* @param {string} username
104168
* @param {boolean} count_private
@@ -166,13 +230,7 @@ async function fetchStats(
166230
stats.contributedTo = user.repositoriesContributedTo.totalCount;
167231

168232
// Retrieve stars while filtering out repositories to be hidden
169-
stats.totalStars = user.repositories.nodes
170-
.filter((data) => {
171-
return !repoToHide[data.name];
172-
})
173-
.reduce((prev, curr) => {
174-
return prev + curr.stargazers.totalCount;
175-
}, 0);
233+
stats.totalStars = await totalStarsFetcher(username, repoToHide);
176234

177235
stats.rank = calculateRank({
178236
totalCommits: stats.totalCommits,

tests/api.test.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,20 @@ const data = {
4040
followers: { totalCount: 0 },
4141
repositories: {
4242
totalCount: 1,
43+
},
44+
},
45+
},
46+
};
47+
48+
const repositoriesData = {
49+
data: {
50+
user: {
51+
repositories: {
4352
nodes: [{ stargazers: { totalCount: 100 } }],
53+
pageInfo: {
54+
hasNextPage: false,
55+
cursor: "cursor",
56+
},
4457
},
4558
},
4659
},
@@ -70,7 +83,11 @@ const faker = (query, data) => {
7083
setHeader: jest.fn(),
7184
send: jest.fn(),
7285
};
73-
mock.onPost("https://api.github.com/graphql").reply(200, data);
86+
mock
87+
.onPost("https://api.github.com/graphql")
88+
.replyOnce(200, data)
89+
.onPost("https://api.github.com/graphql")
90+
.replyOnce(200, repositoriesData);
7491

7592
return { req, res };
7693
};
@@ -138,7 +155,6 @@ describe("Test /api/", () => {
138155

139156
it("should have proper cache", async () => {
140157
const { req, res } = faker({}, data);
141-
mock.onPost("https://api.github.com/graphql").reply(200, data);
142158

143159
await api(req, res);
144160

tests/fetchStats.test.js

+89-6
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,61 @@ const data = {
1919
followers: { totalCount: 100 },
2020
repositories: {
2121
totalCount: 5,
22+
},
23+
},
24+
},
25+
};
26+
27+
const firstRepositoriesData = {
28+
data: {
29+
user: {
30+
repositories: {
2231
nodes: [
2332
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
2433
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
2534
{ name: "test-repo-3", stargazers: { totalCount: 100 } },
35+
],
36+
pageInfo: {
37+
hasNextPage: true,
38+
cursor: "cursor",
39+
},
40+
},
41+
},
42+
},
43+
};
44+
45+
const secondRepositoriesData = {
46+
data: {
47+
user: {
48+
repositories: {
49+
nodes: [
2650
{ name: "test-repo-4", stargazers: { totalCount: 50 } },
2751
{ name: "test-repo-5", stargazers: { totalCount: 50 } },
2852
],
53+
pageInfo: {
54+
hasNextPage: false,
55+
cursor: "cursor",
56+
},
57+
},
58+
},
59+
},
60+
};
61+
62+
const repositoriesWithZeroStarsData = {
63+
data: {
64+
user: {
65+
repositories: {
66+
nodes: [
67+
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
68+
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
69+
{ name: "test-repo-3", stargazers: { totalCount: 100 } },
70+
{ name: "test-repo-4", stargazers: { totalCount: 0 } },
71+
{ name: "test-repo-5", stargazers: { totalCount: 0 } },
72+
],
73+
pageInfo: {
74+
hasNextPage: true,
75+
cursor: "cursor",
76+
},
2977
},
3078
},
3179
},
@@ -44,14 +92,22 @@ const error = {
4492

4593
const mock = new MockAdapter(axios);
4694

95+
beforeEach(() => {
96+
mock
97+
.onPost("https://api.github.com/graphql")
98+
.replyOnce(200, data)
99+
.onPost("https://api.github.com/graphql")
100+
.replyOnce(200, firstRepositoriesData)
101+
.onPost("https://api.github.com/graphql")
102+
.replyOnce(200, secondRepositoriesData);
103+
});
104+
47105
afterEach(() => {
48106
mock.reset();
49107
});
50108

51109
describe("Test fetchStats", () => {
52110
it("should fetch correct stats", async () => {
53-
mock.onPost("https://api.github.com/graphql").reply(200, data);
54-
55111
let stats = await fetchStats("anuraghazra");
56112
const rank = calculateRank({
57113
totalCommits: 100,
@@ -74,7 +130,38 @@ describe("Test fetchStats", () => {
74130
});
75131
});
76132

133+
it("should stop fetching when there are repos with zero stars", async () => {
134+
mock.reset();
135+
mock
136+
.onPost("https://api.github.com/graphql")
137+
.replyOnce(200, data)
138+
.onPost("https://api.github.com/graphql")
139+
.replyOnce(200, repositoriesWithZeroStarsData);
140+
141+
let stats = await fetchStats("anuraghazra");
142+
const rank = calculateRank({
143+
totalCommits: 100,
144+
totalRepos: 5,
145+
followers: 100,
146+
contributions: 61,
147+
stargazers: 300,
148+
prs: 300,
149+
issues: 200,
150+
});
151+
152+
expect(stats).toStrictEqual({
153+
contributedTo: 61,
154+
name: "Anurag Hazra",
155+
totalCommits: 100,
156+
totalIssues: 200,
157+
totalPRs: 300,
158+
totalStars: 300,
159+
rank,
160+
});
161+
});
162+
77163
it("should throw error", async () => {
164+
mock.reset();
78165
mock.onPost("https://api.github.com/graphql").reply(200, error);
79166

80167
await expect(fetchStats("anuraghazra")).rejects.toThrow(
@@ -83,8 +170,6 @@ describe("Test fetchStats", () => {
83170
});
84171

85172
it("should fetch and add private contributions", async () => {
86-
mock.onPost("https://api.github.com/graphql").reply(200, data);
87-
88173
let stats = await fetchStats("anuraghazra", true);
89174
const rank = calculateRank({
90175
totalCommits: 150,
@@ -108,7 +193,6 @@ describe("Test fetchStats", () => {
108193
});
109194

110195
it("should fetch total commits", async () => {
111-
mock.onPost("https://api.github.com/graphql").reply(200, data);
112196
mock
113197
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
114198
.reply(200, { total_count: 1000 });
@@ -136,7 +220,6 @@ describe("Test fetchStats", () => {
136220
});
137221

138222
it("should exclude stars of the `test-repo-1` repository", async () => {
139-
mock.onPost("https://api.github.com/graphql").reply(200, data);
140223
mock
141224
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
142225
.reply(200, { total_count: 1000 });

0 commit comments

Comments
 (0)