Skip to content

Add [GithubCheckRuns] service #7759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ead8ce3
Add [GithubCheckRuns] service
mbtools Mar 24, 2022
0110510
Adjust ref parameter
mbtools Mar 28, 2022
964a7f7
Merge branch 'badges:master' into master
mbtools Sep 27, 2022
29e127b
Merge branch 'badges:master' into master
mbtools Sep 29, 2022
c5403b1
Rework
mbtools Sep 29, 2022
b472c2f
Prettier
mbtools Sep 29, 2022
7ec85af
Prettier
mbtools Sep 29, 2022
b3dc384
Function
mbtools Sep 29, 2022
553952c
Prettier
mbtools Oct 5, 2022
9d27774
Merge branch 'master' into master
mbtools Oct 5, 2022
0ef2b32
Merge branch 'master' into master
mbtools Oct 25, 2022
dcafc80
Change CR to LF
mbtools Oct 25, 2022
f2ebfc5
Merge branch 'master' into master
mbtools Nov 27, 2022
7e246bd
Merge branch 'master' into master
mbtools Jan 1, 2023
319d400
Merge branch 'master' into master
mbtools Jan 18, 2023
f173765
Merge branch 'master' into master
mbtools Feb 6, 2023
d0a2891
Merge branch 'master' into master
mbtools Feb 27, 2023
cd37660
Merge branch 'master' into master
mbtools May 17, 2023
81bf221
Merge branch 'master' into master
mbtools Jun 19, 2023
3b3e315
Adjust after #9233
mbtools Jun 19, 2023
9a3bac5
Lint camelCase
mbtools Jun 19, 2023
75afba7
Fix camelCase
mbtools Jun 19, 2023
0d24675
Merge branch 'master' into master
mbtools Jun 19, 2023
67f40a9
Merge branch 'master' into master
mbtools Aug 27, 2023
6de0c3f
Merge branch 'master' into master
mbtools Sep 4, 2023
485444b
Fix prettier
mbtools Sep 5, 2023
c525399
Merge branch 'master' into master
mbtools Sep 15, 2023
c375851
Merge branch 'master' into master
mbtools Oct 12, 2023
448a927
Merge branch 'badges:master' into master
mbtools Oct 23, 2023
02ea3c4
Merge branch 'badges:master' into master
mbtools Nov 21, 2023
725218e
Switch to openAPI spec for examples
mbtools Nov 27, 2023
40852ba
Fix type of parameters
mbtools Nov 27, 2023
10dc5f2
Fix too many brackets
mbtools Nov 27, 2023
471e3aa
Merge branch 'master' into master
mbtools Jan 2, 2024
9391823
Merge branch 'master' into master
mbtools Jan 31, 2024
b396285
Merge branch 'master' into master
mbtools Feb 16, 2024
cec5d35
Merge branch 'master' into master
mbtools Feb 29, 2024
5872f7d
Merge branch 'master' into master
mbtools Mar 16, 2024
acd468b
Merge branch 'master' into master
mbtools Mar 25, 2024
bd13e04
Merge branch 'master' into master
mbtools Apr 29, 2024
ae7933c
Merge branch 'master' into master
mbtools May 5, 2024
c523f81
Lint
mbtools May 5, 2024
53ecc54
Add optional name filter
mbtools May 10, 2024
94ea16c
Merge branch 'master' into master
mbtools May 10, 2024
344c3aa
Update tests
mbtools May 10, 2024
5df9edb
Merge branch 'master' into master
mbtools May 22, 2024
9eaa065
Remove logo
mbtools May 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions services/github/github-check-runs.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import Joi from 'joi'
import countBy from 'lodash.countby'
import { nonNegativeInteger } from '../validators.js'
import { renderBuildStatusBadge } from '../build-status.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, errorMessagesFor } from './github-helpers.js'

const schema = Joi.object({
total_count: nonNegativeInteger,
check_runs: Joi.array()
.items(
Joi.object({
status: Joi.equal('completed', 'in_progress', 'queued').required(),
conclusion: Joi.equal(
'action_required',
'cancelled',
'failure',
'neutral',
'skipped',
'success',
'timed_out',
null
).required(),
})
)
.default([]),
}).required()

export default class GithubCheckRuns extends GithubAuthV3Service {
static category = 'build'
static route = {
base: 'github/checks-runs',
pattern: ':user/:repo/:ref+',
}

static examples = [
{
title: 'GitHub branch checks runs',
namedParams: {
user: 'badges',
repo: 'shields',
ref: 'master',
},
staticPreview: renderBuildStatusBadge({
status: 'success',
}),
keywords: ['status'],
documentation,
},
{
title: 'GitHub commit checks runs',
namedParams: {
user: 'badges',
repo: 'shields',
ref: '91b108d4b7359b2f8794a4614c11cb1157dc9fff',
},
staticPreview: renderBuildStatusBadge({
status: 'success',
}),
keywords: ['status'],
documentation,
},
{
title: 'GitHub tag checks runs',
namedParams: {
user: 'badges',
repo: 'shields',
ref: '3.3.0',
},
staticPreview: renderBuildStatusBadge({
status: 'success',
}),
keywords: ['status'],
documentation,
},
]

static defaultBadgeData = { label: 'checks', namedLogo: 'github' }

static transform({ total_count, check_runs }) {
return {
total: total_count,
statusCounts: countBy(check_runs, 'status'),
conclusionCounts: countBy(check_runs, 'conclusion'),
}
}

static mapState({ total, statusCounts, conclusionCounts }) {
let state
if (total === 0) {
state = 'no check runs'
} else if (statusCounts.queued) {
state = 'queued'
} else if (statusCounts.in_progress) {
state = 'pending'
} else if (statusCounts.completed) {
// all check runs are completed, now evaluate conclusions
const orangeStates = ['action_required', 'stale']
const redStates = ['cancelled', 'failure', 'timed_out']

// assume "passing (green)"
state = 'passing'
for (const stateValue of Object.keys(conclusionCounts)) {
if (orangeStates.includes(stateValue)) {
// orange state renders "passing (orange)"
state = 'partially succeeded'
} else if (redStates.includes(stateValue)) {
// red state renders "failing (red)"
state = 'failing'
break
}
}
} else {
state = 'unknown status'
}
return state
}

async handle({ user, repo, ref }) {
// https://docs.github.com/en/rest/checks/runs#list-check-runs-for-a-git-reference
const json = await this._requestJson({
url: `/repos/${user}/${repo}/commits/${ref}/check-runs`,
errorMessages: errorMessagesFor('ref or repo not found'),
schema,
})

const state = this.constructor.mapState(this.constructor.transform(json))

return renderBuildStatusBadge({ status: state })
}
}
42 changes: 42 additions & 0 deletions services/github/github-check-runs.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { test, given } from 'sazerac'
import GithubCheckRuns from './github-check-runs.service.js'

describe('GithubCheckRuns', function () {
test(GithubCheckRuns.mapState, () => {
given({
total: 0,
statusCounts: null,
conclusionCounts: null,
}).expect('no check runs')
given({
total: 1,
statusCounts: { queued: 1 },
conclusionCounts: null,
}).expect('queued')
given({
total: 1,
statusCounts: { in_progress: 1 },
conclusionCounts: null,
}).expect('pending')
given({
total: 1,
statusCounts: { completed: 1 },
conclusionCounts: { success: 1 },
}).expect('passing')
given({
total: 2,
statusCounts: { completed: 2 },
conclusionCounts: { success: 1, stale: 1 },
}).expect('partially succeeded')
given({
total: 3,
statusCounts: { completed: 3 },
conclusionCounts: { success: 1, stale: 1, failure: 1 },
}).expect('failing')
given({
total: 1,
statusCounts: { somethingelse: 1 },
conclusionCounts: null,
}).expect('unknown status')
})
})
24 changes: 24 additions & 0 deletions services/github/github-check-runs.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createServiceTester } from '../tester.js'
import { isBuildStatus } from '../build-status.js'
export const t = await createServiceTester()

t.create('check runs - for branch')
.get('/badges/shields/master.json')
.expectBadge({
label: 'checks',
message: isBuildStatus,
})

t.create('check runs - no tests')
.get('/badges/shields/5d4ab86b1b5ddfb3c4a70a70bd19932c52603b8c.json')
.expectBadge({
label: 'checks',
message: 'no check runs',
})

t.create('check runs - nonexistent ref')
.get('/badges/shields/this-ref-does-not-exist.json')
.expectBadge({
label: 'checks',
message: 'ref or repo not found',
})