From 0166b64986c523e9bb1a2b46c4290956e70c8fc3 Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Fri, 4 Feb 2022 05:44:40 +0100 Subject: [PATCH] feat(plugins/notable): add issues, prs and relative stars badges (#841) --- source/plugins/notable/index.mjs | 46 ++++++++++++++++--- source/plugins/notable/metadata.yml | 17 +++++++ .../notable/queries/contributions.graphql | 8 +++- source/plugins/notable/queries/issues.graphql | 15 ++++++ source/templates/classic/partials/notable.ejs | 28 +++++++++-- source/templates/classic/style.css | 2 +- .../github/graphql/notable.contributions.mjs | 2 + .../api/github/graphql/notable.issues.mjs | 35 ++++++++++++++ 8 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 source/plugins/notable/queries/issues.graphql create mode 100644 tests/mocks/api/github/graphql/notable.issues.mjs diff --git a/source/plugins/notable/index.mjs b/source/plugins/notable/index.mjs index 7da5bebd..6a3cb514 100644 --- a/source/plugins/notable/index.mjs +++ b/source/plugins/notable/index.mjs @@ -7,29 +7,29 @@ export default async function({login, q, imports, rest, graphql, data, account, return null //Load inputs - let {filter, skipped, repositories, from, indepth} = imports.metadata.plugins.notable.inputs({data, account, q}) + let {filter, skipped, repositories, types, from, indepth} = imports.metadata.plugins.notable.inputs({data, account, q}) skipped.push(...data.shared["repositories.skipped"]) //Iterate through contributed repositories - const commits = [] + let contributions = [] { let cursor = null let pushed = 0 do { console.debug(`metrics/compute/${login}/plugins > notable > retrieving contributed repositories after ${cursor}`) - const {user:{repositoriesContributedTo:{edges}}} = await graphql(queries.notable.contributions({login, after:cursor ? `after: "${cursor}"` : "", repositories:data.shared["repositories.batch"] || 100})) + const {user:{repositoriesContributedTo:{edges}}} = await graphql(queries.notable.contributions({login, types:types.map(x => x.toLocaleUpperCase()).join(", "), after:cursor ? `after: "${cursor}"` : "", repositories:data.shared["repositories.batch"] || 100})) cursor = edges?.[edges?.length - 1]?.cursor edges .filter(({node}) => !((skipped.includes(node.nameWithOwner.toLocaleLowerCase())) || (skipped.includes(node.nameWithOwner.split("/")[1].toLocaleLowerCase())))) .filter(({node}) => ({all:true, organization:node.isInOrganization, user:!node.isInOrganization}[from])) .filter(({node}) => imports.ghfilter(filter, {name:node.nameWithOwner, user:node.owner.login, stars:node.stargazers.totalCount, watchers:node.watchers.totalCount, forks:node.forks.totalCount})) - .map(({node}) => commits.push({handle:node.nameWithOwner, stars:node.stargazers.totalCount, organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl})) + .map(({node}) => contributions.push({handle:node.nameWithOwner, stars:node.stargazers.totalCount, issues:node.issues.totalCount, pulls:node.pullRequests.totalCount, organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl})) pushed = edges.length } while ((pushed) && (cursor)) } //Set contributions - let contributions = (await Promise.all(commits.map(async ({handle, stars, avatarUrl, organization}) => ({name:handle.split("/").shift(), handle, stars, avatar:await imports.imgb64(avatarUrl), organization})))).sort((a, b) => a.name.localeCompare(b.name)) + contributions = (await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name:handle.split("/").shift(), handle, stars, issues, pulls, avatar:await imports.imgb64(avatarUrl), organization})))).sort((a, b) => a.name.localeCompare(b.name)) console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`) //Extras features @@ -37,6 +37,36 @@ export default async function({login, q, imports, rest, graphql, data, account, //Indepth if (indepth) { console.debug(`metrics/compute/${login}/plugins > notable > indepth`) + + //Fetch issues + const issues = {} + if (types.includes("issue")) { + let cursor = null + let pushed = 0 + do { + console.debug(`metrics/compute/${login}/plugins > notable > retrieving user issues after ${cursor}`) + const {user:{issues:{edges}}} = await graphql(queries.notable.issues({login, type:"issues", after:cursor ? `after: "${cursor}"` : ""})) + cursor = edges?.[edges?.length - 1]?.cursor + edges.map(({node:{repository:{nameWithOwner:repository}}}) => issues[repository] = (issues[repositories] ?? 0) + 1) + pushed = edges.length + } while ((pushed) && (cursor)) + } + + //Fetch pull requests + const pulls = {} + if (types.includes("pull_request")) { + let cursor = null + let pushed = 0 + do { + console.debug(`metrics/compute/${login}/plugins > notable > retrieving user pull requests after ${cursor}`) + const {user:{pullRequests:{edges}}} = await graphql(queries.notable.issues({login, type:"pullRequests", after:cursor ? `after: "${cursor}"` : ""})) + cursor = edges?.[edges?.length - 1]?.cursor + edges.map(({node:{repository:{nameWithOwner:repository}}}) => pulls[repository] = (pulls[repositories] ?? 0) + 1) + pushed = edges.length + } while ((pushed) && (cursor)) + } + + //Fetch commits for (const contribution of contributions) { //Prepare data const {handle, stars} = contribution @@ -59,8 +89,10 @@ export default async function({login, q, imports, rest, graphql, data, account, commits, percentage:commits / contribution.history, maintainer:maintainers.includes(login), + issues:issues[handle] ?? 0, + pulls:pulls[handle] ?? 0, get stars() { - return this.maintainer ? stars : this.percentage * stars + return Math.round(this.maintainer ? stars : this.percentage * stars) }, } console.debug(`metrics/compute/${login}/plugins > notable > indepth > successfully processed ${owner}/${repo}`) @@ -106,7 +138,7 @@ export default async function({login, q, imports, rest, graphql, data, account, } //Results - return {contributions} + return {contributions, types} } //Handle errors catch (error) { diff --git a/source/plugins/notable/metadata.yml b/source/plugins/notable/metadata.yml index de4b4ef2..a87875d7 100644 --- a/source/plugins/notable/metadata.yml +++ b/source/plugins/notable/metadata.yml @@ -63,3 +63,20 @@ inputs: type: boolean default: no extras: yes + + plugin_notable_types: + description: | + Contribution types filter + + Use a combination of below values to include repositories where: + - `commit`: a commit on default branch was made + - `pull_request`: a pull request was opened + - `issue`: an issue was opened + type: array + format: comma-separated + default: commit + example: commit, pull_request + values: + - commit + - pull_request + - issue diff --git a/source/plugins/notable/queries/contributions.graphql b/source/plugins/notable/queries/contributions.graphql index fd70cef9..6bc04695 100644 --- a/source/plugins/notable/queries/contributions.graphql +++ b/source/plugins/notable/queries/contributions.graphql @@ -1,6 +1,6 @@ query NotableContributions { user(login: "$login") { - repositoriesContributedTo($after first: $repositories, contributionTypes: COMMIT, orderBy: { field: STARGAZERS, direction: DESC }) { + repositoriesContributedTo($after first: $repositories, contributionTypes: [$types], orderBy: { field: STARGAZERS, direction: DESC }) { edges { cursor node { @@ -19,6 +19,12 @@ query NotableContributions { stargazers { totalCount } + issues { + totalCount + } + pullRequests { + totalCount + } } } } diff --git a/source/plugins/notable/queries/issues.graphql b/source/plugins/notable/queries/issues.graphql new file mode 100644 index 00000000..3b75dffd --- /dev/null +++ b/source/plugins/notable/queries/issues.graphql @@ -0,0 +1,15 @@ +query NotableIssues { + user(login: "$login") { + $type($after first: 100, orderBy: {field: CREATED_AT, direction: DESC}) { + totalCount + edges { + cursor + node { + repository { + nameWithOwner + } + } + } + } + } +} \ No newline at end of file diff --git a/source/templates/classic/partials/notable.ejs b/source/templates/classic/partials/notable.ejs index 403ed8d2..8e922fc0 100644 --- a/source/templates/classic/partials/notable.ejs +++ b/source/templates/classic/partials/notable.ejs @@ -16,7 +16,7 @@ <% } else { %> <% if (plugins.notable.contributions.length) { %>
- <% for (const {name, avatar, organization, user:{commits = 0, maintainer = false, percentage = 0} = {}} of plugins.notable.contributions) { %> + <% for (const {name, avatar, organization, user:{commits = 0, stars = 0, issues = 0, pulls = 0, maintainer = false, percentage = 0} = {}, ...total} of plugins.notable.contributions) { %>
.2 ? "a" : percentage > .1 ? "b" : percentage > .05 ? "c" : "" %> "> avatar" src="<%= avatar %>" width="16" height="16" /> @<%= name %> @@ -26,8 +26,30 @@ <%= commits %> - + <% } %> + <% if (stars) { %> + + + + <%= f(stars) %> + + + <% } %> + <% if (issues) { %> + + + + <%= issues %> + + <% } %> + <% if (pulls) { %> + + + + <%= pulls %> + + <% } %>
<% } %>
@@ -36,7 +58,7 @@
- No recent contributions in organization repositories found + No recent contributions matching filters found
diff --git a/source/templates/classic/style.css b/source/templates/classic/style.css index f03ee8a8..268444cb 100644 --- a/source/templates/classic/style.css +++ b/source/templates/classic/style.css @@ -205,7 +205,7 @@ margin: 0 4px; color: inherit; } - .contribution .commits-icon { + .contribution .icon { fill: currentColor; width: 10px; height: 10px; diff --git a/tests/mocks/api/github/graphql/notable.contributions.mjs b/tests/mocks/api/github/graphql/notable.contributions.mjs index fd6fd7ab..f438ddb0 100644 --- a/tests/mocks/api/github/graphql/notable.contributions.mjs +++ b/tests/mocks/api/github/graphql/notable.contributions.mjs @@ -25,6 +25,8 @@ export default function({ faker, query, login = faker.internet.userName() }) { stargazers: { totalCount: faker.datatype.number(1000) }, watchers: { totalCount: faker.datatype.number(1000) }, forks: { totalCount: faker.datatype.number(1000) }, + issues: { totalCount: faker.datatype.number(1000) }, + pullRequests: { totalCount: faker.datatype.number(1000) }, }, }, ], diff --git a/tests/mocks/api/github/graphql/notable.issues.mjs b/tests/mocks/api/github/graphql/notable.issues.mjs new file mode 100644 index 00000000..0a16effc --- /dev/null +++ b/tests/mocks/api/github/graphql/notable.issues.mjs @@ -0,0 +1,35 @@ +/**Mocked data */ +export default function({ faker, query, login = faker.internet.userName() }) { + console.debug("metrics/compute/mocks > mocking graphql api result > notable/issues") + return /after: "MOCKED_CURSOR"/m.test(query) + ? ({ + user: { + issues: { + edges: [], + }, + pullRequests: { + edges: [], + }, + }, + }) + : ({ + user: { + issues: { + totalCount: faker.datatype.number(1000), + edges: [ + { + cursor: "MOCKED_CURSOR", + node: { + repository:{ + nameWithOwner: `${faker.internet.userName()}/${faker.lorem.slug()}` + } + }, + }, + ], + }, + get pullRequests() { + return this.issues + } + }, + }) +} \ No newline at end of file