From 5fb41f75277864d398dda954f3995e3113fbcb98 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Mon, 21 Nov 2022 11:08:05 +0100 Subject: [PATCH] feat: add `include_orgs` card variable This pull request adds experimental Organization support. Users can include stats from Organization repositories to which they are collaborators using the `include_orgs` card argument. Please be aware that because of #1852, only the first 100 repositories are used. Including organization, stats might therefore skew your results. Co-authored-by: Raymond Nook <59678453+developStorm@users.noreply.github.com> --- api/index.js | 2 ++ api/top-langs.js | 2 ++ readme.md | 2 ++ src/common/utils.js | 2 +- src/fetchers/stats-fetcher.js | 31 ++++++++++++++++++++------- src/fetchers/top-languages-fetcher.js | 16 ++++++++++---- tests/fetchStats.test.js | 6 ++++-- tests/fetchTopLanguages.test.js | 2 +- 8 files changed, 47 insertions(+), 16 deletions(-) diff --git a/api/index.js b/api/index.js index b449d43b49080..491a3d25c1bb5 100644 --- a/api/index.js +++ b/api/index.js @@ -21,6 +21,7 @@ export default async (req, res) => { show_icons, count_private, include_all_commits, + include_orgs, line_height, title_color, ring_color, @@ -52,6 +53,7 @@ export default async (req, res) => { username, parseBoolean(count_private), parseBoolean(include_all_commits), + parseBoolean(include_orgs), parseArray(exclude_repo), ); diff --git a/api/top-langs.js b/api/top-langs.js index d183d3b455ca0..1ce30418e80d8 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -24,6 +24,7 @@ export default async (req, res) => { cache_seconds, layout, langs_count, + include_orgs, exclude_repo, custom_title, locale, @@ -43,6 +44,7 @@ export default async (req, res) => { try { const topLangs = await fetchTopLanguages( username, + include_orgs, parseArray(exclude_repo), ); diff --git a/readme.md b/readme.md index bfe042fcc2032..ba8810bb3bea5 100644 --- a/readme.md +++ b/readme.md @@ -280,6 +280,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width. Default: `false`. - `show_icons` - _(boolean)_. Default: `false`. - `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`. +- `include_orgs` - Include stats from organization repositories. Default: `false`. - `count_private` - Count private commits _(boolean)_. Default: `false`. - `line_height` - Sets the line height between text _(number)_. Default: `25`. - `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. @@ -302,6 +303,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `layout` - Switch between two available layouts `default` & `compact`. Default: `default`. - `card_width` - Set the card's width manually _(number)_. Default `300`. - `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`. +- `include_orgs` - Include language stats from organization repositories. Default: `false`. - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. - `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`. diff --git a/src/common/utils.js b/src/common/utils.js index 1215fc9ac8cc2..147f5408f81f3 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -77,7 +77,7 @@ const isValidHexColor = (hexColor) => { /** * Returns boolean if value is either "true" or "false" else the value as it is. * - * @param {string | boolean} value The value to parse. + * @param {string | boolean| undefined} value The value to parse. * @returns {boolean | undefined } The parsed value. */ const parseBoolean = (value) => { diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index 7f6cb9e5e95b4..9570ade057643 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -22,7 +22,7 @@ const fetcher = (variables, token) => { return request( { query: ` - query userInfo($login: String!) { + query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) { user(login: $login) { name login @@ -45,7 +45,7 @@ const fetcher = (variables, token) => { followers { totalCount } - repositories(ownerAffiliations: OWNER) { + repositories(ownerAffiliations: $ownerAffiliations) { totalCount } } @@ -70,9 +70,9 @@ const repositoriesFetcher = (variables, token) => { return request( { query: ` - query userInfo($login: String!, $after: String) { + query userInfo($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) { user(login: $login) { - repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) { + repositories(first: 100, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) { nodes { name stargazers { @@ -141,15 +141,21 @@ const totalCommitsFetcher = async (username) => { * Fetch all the stars for all the repositories of a given username. * * @param {string} username GitHub username. + * @param {boolean} include_orgs Include stats from organization repos. * @param {array} repoToHide Repositories to hide. * @returns {Promise} Total stars. */ -const totalStarsFetcher = async (username, repoToHide) => { +const totalStarsFetcher = async (username, include_orgs, repoToHide) => { let nodes = []; let hasNextPage = true; let endCursor = null; while (hasNextPage) { - const variables = { login: username, first: 100, after: endCursor }; + const variables = { + login: username, + first: 100, + after: endCursor, + ownerAffiliations: include_orgs ? ["OWNER", "COLLABORATOR"] : ["OWNER"], + }; let res = await retryer(repositoriesFetcher, variables); if (res.data.errors) { @@ -183,12 +189,14 @@ const totalStarsFetcher = async (username, repoToHide) => { * @param {string} username GitHub username. * @param {boolean} count_private Include private contributions. * @param {boolean} include_all_commits Include all commits. + * @param {boolean} include_orgs Include stats from organization repos. * @returns {Promise} Stats data. */ const fetchStats = async ( username, count_private = false, include_all_commits = false, + include_orgs = false, exclude_repo = [], ) => { if (!username) throw new MissingParamError(["username"]); @@ -203,7 +211,10 @@ const fetchStats = async ( rank: { level: "C", score: 0 }, }; - let res = await retryer(fetcher, { login: username }); + let res = await retryer(fetcher, { + login: username, + ownerAffiliations: include_orgs ? ["OWNER", "COLLABORATOR"] : ["OWNER"], + }); // Catch GraphQL errors. if (res.data.errors) { @@ -259,7 +270,11 @@ const fetchStats = async ( stats.contributedTo = user.repositoriesContributedTo.totalCount; // Retrieve stars while filtering out repositories to be hidden - stats.totalStars = await totalStarsFetcher(username, repoToHide); + stats.totalStars = await totalStarsFetcher( + username, + include_orgs, + repoToHide, + ); stats.rank = calculateRank({ totalCommits: stats.totalCommits, diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js index 86d794435be08..a962be1808096 100644 --- a/src/fetchers/top-languages-fetcher.js +++ b/src/fetchers/top-languages-fetcher.js @@ -19,10 +19,10 @@ const fetcher = (variables, token) => { return request( { query: ` - query userInfo($login: String!) { + query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) { user(login: $login) { # fetch only owner repos & not forks - repositories(ownerAffiliations: OWNER, isFork: false, first: 100) { + repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) { nodes { name languages(first: 10, orderBy: {field: SIZE, direction: DESC}) { @@ -51,13 +51,21 @@ const fetcher = (variables, token) => { * Fetch top languages for a given username. * * @param {string} username GitHub username. + * @param {boolean} include_orgs Include stats from organization repos. * @param {string[]} exclude_repo List of repositories to exclude. * @returns {Promise} Top languages data. */ -const fetchTopLanguages = async (username, exclude_repo = []) => { +const fetchTopLanguages = async ( + username, + include_orgs = false, + exclude_repo = [], +) => { if (!username) throw new MissingParamError(["username"]); - const res = await retryer(fetcher, { login: username }); + const res = await retryer(fetcher, { + login: username, + ownerAffiliations: include_orgs ? ["OWNER", "COLLABORATOR"] : ["OWNER"], + }); if (res.data.errors) { logger.error(res.data.errors); diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js index 192146ea5fbe0..58f2a40b9a02c 100644 --- a/tests/fetchStats.test.js +++ b/tests/fetchStats.test.js @@ -201,7 +201,7 @@ describe("Test fetchStats", () => { .onGet("https://api.github.com/search/commits?q=author:anuraghazra") .reply(200, { total_count: 1000 }); - let stats = await fetchStats("anuraghazra", true, true); + let stats = await fetchStats("anuraghazra", true, true, false); const rank = calculateRank({ totalCommits: 1050, totalRepos: 5, @@ -230,7 +230,9 @@ describe("Test fetchStats", () => { .onGet("https://api.github.com/search/commits?q=author:anuraghazra") .reply(200, { total_count: 1000 }); - let stats = await fetchStats("anuraghazra", true, true, ["test-repo-1"]); + let stats = await fetchStats("anuraghazra", true, true, false, [ + "test-repo-1", + ]); const rank = calculateRank({ totalCommits: 1050, totalRepos: 5, diff --git a/tests/fetchTopLanguages.test.js b/tests/fetchTopLanguages.test.js index 24416cd294525..b994e616104c0 100644 --- a/tests/fetchTopLanguages.test.js +++ b/tests/fetchTopLanguages.test.js @@ -81,7 +81,7 @@ describe("FetchTopLanguages", () => { it("should fetch correct language data while excluding the 'test-repo-1' repository", async () => { mock.onPost("https://api.github.com/graphql").reply(200, data_langs); - let repo = await fetchTopLanguages("anuraghazra", ["test-repo-1"]); + let repo = await fetchTopLanguages("anuraghazra", false, ["test-repo-1"]); expect(repo).toStrictEqual({ HTML: { color: "#0f0",