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 <rick.staa@outlook.com>
This commit is contained in:
Matteo Pierro
2022-10-04 12:10:50 +01:00
committed by GitHub
parent acbc03dc0f
commit af97e5765b
3 changed files with 175 additions and 18 deletions
+68 -10
View File
@@ -29,10 +29,10 @@ const fetcher = (variables, token) => {
totalCommitContributions
restrictedContributionsCount
}
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
repositoriesContributedTo(contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
totalCount
}
pullRequests(first: 1) {
pullRequests {
totalCount
}
openIssues: issues(states: OPEN) {
@@ -44,14 +44,41 @@ const fetcher = (variables, token) => {
followers {
totalCount
}
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
repositories(ownerAffiliations: OWNER) {
totalCount
}
}
}
`,
variables,
},
{
Authorization: `bearer ${token}`,
},
);
};
/**
* @param {import('axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const repositoriesFetcher = (variables, token) => {
return request(
{
query: `
query userInfo($login: String!, $after: String) {
user(login: $login) {
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
nodes {
name
stargazers {
totalCount
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
@@ -99,6 +126,43 @@ const totalCommitsFetcher = async (username) => {
return 0;
};
/**
* Fetch all the stars for all the repositories of a given username
* @param {string} username
* @param {array} repoToHide
*/
const totalStarsFetcher = async (username, repoToHide) => {
let nodes = [];
let hasNextPage = true;
let endCursor = null;
while (hasNextPage) {
const variables = { login: username, first: 100, after: endCursor };
let res = await retryer(repositoriesFetcher, variables);
if (res.data.errors) {
logger.error(res.data.errors);
throw new CustomError(
res.data.errors[0].message || "Could not fetch user",
CustomError.USER_NOT_FOUND,
);
}
const allNodes = res.data.data.user.repositories.nodes;
const nodesWithStars = allNodes.filter(
(node) => node.stargazers.totalCount !== 0,
);
nodes.push(...nodesWithStars);
hasNextPage =
allNodes.length === nodesWithStars.length &&
res.data.data.user.repositories.pageInfo.hasNextPage;
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
}
return nodes
.filter((data) => !repoToHide[data.name])
.reduce((prev, curr) => prev + curr.stargazers.totalCount, 0);
};
/**
* @param {string} username
* @param {boolean} count_private
@@ -166,13 +230,7 @@ async function fetchStats(
stats.contributedTo = user.repositoriesContributedTo.totalCount;
// Retrieve stars while filtering out repositories to be hidden
stats.totalStars = user.repositories.nodes
.filter((data) => {
return !repoToHide[data.name];
})
.reduce((prev, curr) => {
return prev + curr.stargazers.totalCount;
}, 0);
stats.totalStars = await totalStarsFetcher(username, repoToHide);
stats.rank = calculateRank({
totalCommits: stats.totalCommits,
+18 -2
View File
@@ -40,7 +40,20 @@ const data = {
followers: { totalCount: 0 },
repositories: {
totalCount: 1,
},
},
},
};
const repositoriesData = {
data: {
user: {
repositories: {
nodes: [{ stargazers: { totalCount: 100 } }],
pageInfo: {
hasNextPage: false,
cursor: "cursor",
},
},
},
},
@@ -70,7 +83,11 @@ const faker = (query, data) => {
setHeader: jest.fn(),
send: jest.fn(),
};
mock.onPost("https://api.github.com/graphql").reply(200, data);
mock
.onPost("https://api.github.com/graphql")
.replyOnce(200, data)
.onPost("https://api.github.com/graphql")
.replyOnce(200, repositoriesData);
return { req, res };
};
@@ -138,7 +155,6 @@ describe("Test /api/", () => {
it("should have proper cache", async () => {
const { req, res } = faker({}, data);
mock.onPost("https://api.github.com/graphql").reply(200, data);
await api(req, res);
+89 -6
View File
@@ -19,13 +19,61 @@ const data = {
followers: { totalCount: 100 },
repositories: {
totalCount: 5,
},
},
},
};
const firstRepositoriesData = {
data: {
user: {
repositories: {
nodes: [
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
{ name: "test-repo-3", stargazers: { totalCount: 100 } },
],
pageInfo: {
hasNextPage: true,
cursor: "cursor",
},
},
},
},
};
const secondRepositoriesData = {
data: {
user: {
repositories: {
nodes: [
{ name: "test-repo-4", stargazers: { totalCount: 50 } },
{ name: "test-repo-5", stargazers: { totalCount: 50 } },
],
pageInfo: {
hasNextPage: false,
cursor: "cursor",
},
},
},
},
};
const repositoriesWithZeroStarsData = {
data: {
user: {
repositories: {
nodes: [
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
{ name: "test-repo-3", stargazers: { totalCount: 100 } },
{ name: "test-repo-4", stargazers: { totalCount: 0 } },
{ name: "test-repo-5", stargazers: { totalCount: 0 } },
],
pageInfo: {
hasNextPage: true,
cursor: "cursor",
},
},
},
},
@@ -44,14 +92,22 @@ const error = {
const mock = new MockAdapter(axios);
beforeEach(() => {
mock
.onPost("https://api.github.com/graphql")
.replyOnce(200, data)
.onPost("https://api.github.com/graphql")
.replyOnce(200, firstRepositoriesData)
.onPost("https://api.github.com/graphql")
.replyOnce(200, secondRepositoriesData);
});
afterEach(() => {
mock.reset();
});
describe("Test fetchStats", () => {
it("should fetch correct stats", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
@@ -74,7 +130,38 @@ describe("Test fetchStats", () => {
});
});
it("should stop fetching when there are repos with zero stars", async () => {
mock.reset();
mock
.onPost("https://api.github.com/graphql")
.replyOnce(200, data)
.onPost("https://api.github.com/graphql")
.replyOnce(200, repositoriesWithZeroStarsData);
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 100,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
rank,
});
});
it("should throw error", async () => {
mock.reset();
mock.onPost("https://api.github.com/graphql").reply(200, error);
await expect(fetchStats("anuraghazra")).rejects.toThrow(
@@ -83,8 +170,6 @@ describe("Test fetchStats", () => {
});
it("should fetch and add private contributions", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
let stats = await fetchStats("anuraghazra", true);
const rank = calculateRank({
totalCommits: 150,
@@ -108,7 +193,6 @@ describe("Test fetchStats", () => {
});
it("should fetch total commits", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
mock
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
.reply(200, { total_count: 1000 });
@@ -136,7 +220,6 @@ describe("Test fetchStats", () => {
});
it("should exclude stars of the `test-repo-1` repository", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
mock
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
.reply(200, { total_count: 1000 });