feat(plugins/notable): add issues, prs and relative stars badges (#841)
This commit is contained in:
@@ -7,29 +7,29 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//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"])
|
skipped.push(...data.shared["repositories.skipped"])
|
||||||
|
|
||||||
//Iterate through contributed repositories
|
//Iterate through contributed repositories
|
||||||
const commits = []
|
let contributions = []
|
||||||
{
|
{
|
||||||
let cursor = null
|
let cursor = null
|
||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > retrieving contributed repositories after ${cursor}`)
|
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
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
edges
|
edges
|
||||||
.filter(({node}) => !((skipped.includes(node.nameWithOwner.toLocaleLowerCase())) || (skipped.includes(node.nameWithOwner.split("/")[1].toLocaleLowerCase()))))
|
.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}) => ({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}))
|
.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
|
pushed = edges.length
|
||||||
} while ((pushed) && (cursor))
|
} while ((pushed) && (cursor))
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set contributions
|
//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`)
|
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
|
||||||
|
|
||||||
//Extras features
|
//Extras features
|
||||||
@@ -37,6 +37,36 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
//Indepth
|
//Indepth
|
||||||
if (indepth) {
|
if (indepth) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > 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) {
|
for (const contribution of contributions) {
|
||||||
//Prepare data
|
//Prepare data
|
||||||
const {handle, stars} = contribution
|
const {handle, stars} = contribution
|
||||||
@@ -59,8 +89,10 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
commits,
|
commits,
|
||||||
percentage:commits / contribution.history,
|
percentage:commits / contribution.history,
|
||||||
maintainer:maintainers.includes(login),
|
maintainer:maintainers.includes(login),
|
||||||
|
issues:issues[handle] ?? 0,
|
||||||
|
pulls:pulls[handle] ?? 0,
|
||||||
get stars() {
|
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}`)
|
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
|
//Results
|
||||||
return {contributions}
|
return {contributions, types}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
@@ -63,3 +63,20 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
extras: yes
|
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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
query NotableContributions {
|
query NotableContributions {
|
||||||
user(login: "$login") {
|
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 {
|
edges {
|
||||||
cursor
|
cursor
|
||||||
node {
|
node {
|
||||||
@@ -19,6 +19,12 @@ query NotableContributions {
|
|||||||
stargazers {
|
stargazers {
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
|
issues {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
pullRequests {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
source/plugins/notable/queries/issues.graphql
Normal file
15
source/plugins/notable/queries/issues.graphql
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<% } else { %>
|
<% } else { %>
|
||||||
<% if (plugins.notable.contributions.length) { %>
|
<% if (plugins.notable.contributions.length) { %>
|
||||||
<div class="row organization contributions">
|
<div class="row organization contributions">
|
||||||
<% 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) { %>
|
||||||
<div class="organization contribution <%= maintainer ? "s" : percentage > .2 ? "a" : percentage > .1 ? "b" : percentage > .05 ? "c" : "" %> ">
|
<div class="organization contribution <%= maintainer ? "s" : percentage > .2 ? "a" : percentage > .1 ? "b" : percentage > .05 ? "c" : "" %> ">
|
||||||
<img class="<%= organization ? "organization" : "" %> avatar" src="<%= avatar %>" width="16" height="16" />
|
<img class="<%= organization ? "organization" : "" %> avatar" src="<%= avatar %>" width="16" height="16" />
|
||||||
<span class="name">@<%= name %></span>
|
<span class="name">@<%= name %></span>
|
||||||
@@ -26,8 +26,30 @@
|
|||||||
<circle class="gauge-arc" transform="rotate(-90 15 15)" r="12.5" cx="15" cy="15" stroke-dasharray="<%= percentage * 329 %> 329"></circle>
|
<circle class="gauge-arc" transform="rotate(-90 15 15)" r="12.5" cx="15" cy="15" stroke-dasharray="<%= percentage * 329 %> 329"></circle>
|
||||||
<text x="15" y="15" dominant-baseline="central" ><%= commits %></text>
|
<text x="15" y="15" dominant-baseline="central" ><%= commits %></text>
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="commits-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"></path></svg>
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"></path></svg>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% if (stars) { %>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="16" height="16" class="gauge">
|
||||||
|
<circle class="gauge-base" r="12.5" cx="15" cy="15"></circle>
|
||||||
|
<circle class="gauge-arc" transform="rotate(-90 15 15)" r="12.5" cx="15" cy="15" stroke-dasharray="<%= stars/(total.stars||1) * 329 %> 329"></circle>
|
||||||
|
<text x="15" y="15" dominant-baseline="central" ><%= f(stars) %></text>
|
||||||
|
</svg>
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path></svg>
|
||||||
|
<% } %>
|
||||||
|
<% if (issues) { %>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="16" height="16" class="gauge">
|
||||||
|
<circle class="gauge-base" r="12.5" cx="15" cy="15"></circle>
|
||||||
|
<circle class="gauge-arc" transform="rotate(-90 15 15)" r="12.5" cx="15" cy="15" stroke-dasharray="<%= issues/(total.issues||1) * 329 %> 329"></circle>
|
||||||
|
<text x="15" y="15" dominant-baseline="central" ><%= issues %></text>
|
||||||
|
</svg>
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8 9.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path><path fill-rule="evenodd" d="M8 0a8 8 0 100 16A8 8 0 008 0zM1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0z"></path></svg> <% } %>
|
||||||
|
<% if (pulls) { %>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="16" height="16" class="gauge">
|
||||||
|
<circle class="gauge-base" r="12.5" cx="15" cy="15"></circle>
|
||||||
|
<circle class="gauge-arc" transform="rotate(-90 15 15)" r="12.5" cx="15" cy="15" stroke-dasharray="<%= pulls/(total.pulls||1) * 329 %> 329"></circle>
|
||||||
|
<text x="15" y="15" dominant-baseline="central" ><%= pulls %></text>
|
||||||
|
</svg>
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"></path></svg> <% } %>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,7 +58,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm6.5-.25A.75.75 0 017.25 7h1a.75.75 0 01.75.75v2.75h.25a.75.75 0 010 1.5h-2a.75.75 0 010-1.5h.25v-2h-.25a.75.75 0 01-.75-.75zM8 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm6.5-.25A.75.75 0 017.25 7h1a.75.75 0 01.75.75v2.75h.25a.75.75 0 010 1.5h-2a.75.75 0 010-1.5h.25v-2h-.25a.75.75 0 01-.75-.75zM8 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>
|
||||||
No recent contributions in organization repositories found
|
No recent contributions matching filters found
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
.contribution .commits-icon {
|
.contribution .icon {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
stargazers: { totalCount: faker.datatype.number(1000) },
|
stargazers: { totalCount: faker.datatype.number(1000) },
|
||||||
watchers: { totalCount: faker.datatype.number(1000) },
|
watchers: { totalCount: faker.datatype.number(1000) },
|
||||||
forks: { totalCount: faker.datatype.number(1000) },
|
forks: { totalCount: faker.datatype.number(1000) },
|
||||||
|
issues: { totalCount: faker.datatype.number(1000) },
|
||||||
|
pullRequests: { totalCount: faker.datatype.number(1000) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
35
tests/mocks/api/github/graphql/notable.issues.mjs
Normal file
35
tests/mocks/api/github/graphql/notable.issues.mjs
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user