Implementation of PRChecklist feature. Closes #27
Signed-off-by: Pedro Pombeiro <pombeirp@users.noreply.github.com>
This commit is contained in:
parent
c925586e0e
commit
72713b328e
|
@ -2,3 +2,8 @@ _extends: probot-settings
|
||||||
|
|
||||||
github-team:
|
github-team:
|
||||||
slug: 'go'
|
slug: 'go'
|
||||||
|
|
||||||
|
prchecklist:
|
||||||
|
title: 'PR Guildelines checklist'
|
||||||
|
checklist:
|
||||||
|
- "All contribution guidelines were followed"
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.env
|
||||||
|
*.pem
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Description:
|
||||||
|
// Script that listens to GitHub pull requests events
|
||||||
|
// and manages a checklist based on configuration in github-bot.yml
|
||||||
|
// It sets the mergeable status based checklist state
|
||||||
|
//
|
||||||
|
// Dependencies:
|
||||||
|
// github: "^13.1.0"
|
||||||
|
// probot-config: "^0.1.0"
|
||||||
|
//
|
||||||
|
// Author:
|
||||||
|
// virneo
|
||||||
|
|
||||||
|
const fetch = require('node-fetch')
|
||||||
|
const getConfig = require('probot-config')
|
||||||
|
const defaultConfig = require('../lib/config')
|
||||||
|
const botName = 'manage-pr-checklist'
|
||||||
|
|
||||||
|
module.exports = (robot) => {
|
||||||
|
robot.on(
|
||||||
|
[
|
||||||
|
'pull_request.opened',
|
||||||
|
'pull_request.edited'
|
||||||
|
],
|
||||||
|
context => {
|
||||||
|
// Make sure we don't listen to our own messages
|
||||||
|
if (context.isBot) { return }
|
||||||
|
handlePullRequest(context, robot)
|
||||||
|
})
|
||||||
|
robot.on('issue_comment', context => {
|
||||||
|
// Make sure we don't listen to our own messages
|
||||||
|
if (context.isBot) { return }
|
||||||
|
handleIssue(context, robot)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleIssue (context, robot) {
|
||||||
|
if (context.payload.issue.pull_request) {
|
||||||
|
const res = await fetch(context.payload.issue.pull_request.url)
|
||||||
|
const pr = await res.json()
|
||||||
|
context.payload.pull_request = pr
|
||||||
|
return handlePullRequest(context, robot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePullRequest (context, robot) {
|
||||||
|
const config = await getConfig(context, 'github-bot.yml', defaultConfig(robot, '.github/github-bot.yml'))
|
||||||
|
const settings = config ? config['prchecklist'] : null
|
||||||
|
if (!settings) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (settings.title == null) settings.title = ''
|
||||||
|
if (settings.checklist == null) settings.checklist = {}
|
||||||
|
const currentStatus = await getCurrentStatus(context)
|
||||||
|
const {isChecklistComplete, firstCheck} = await verifyChecklist(context, settings)
|
||||||
|
const newStatus = isChecklistComplete ? 'success' : 'pending'
|
||||||
|
const hasChange = firstCheck || currentStatus !== newStatus
|
||||||
|
const logStatus = isChecklistComplete ? '✅' : '⏳'
|
||||||
|
const shortUrl = context.payload.pull_request.url
|
||||||
|
|
||||||
|
if (!hasChange) {
|
||||||
|
robot.log.info(`${botName} - 😐${logStatus} ${shortUrl}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await context.github.repos.createStatus(context.repo({
|
||||||
|
sha: context.payload.pull_request.head.sha,
|
||||||
|
state: newStatus,
|
||||||
|
target_url: 'https://github.com/status-im/status-github-bot.git',
|
||||||
|
description: isChecklistComplete ? 'ready for merge' : 'PR Checklist is incomplete',
|
||||||
|
context: 'PRChecklist'
|
||||||
|
}))
|
||||||
|
|
||||||
|
robot.log.info(`${botName} - 💾${logStatus} ${shortUrl}`)
|
||||||
|
} catch (err) {
|
||||||
|
robot.log.error(`${botName} - Couldn't create status for commits in the PR: ${err}`, context.payload.pull_request.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCurrentStatus (context) {
|
||||||
|
const {data: {statuses}} = await context.github.repos.getCombinedStatusForRef(context.repo({
|
||||||
|
ref: context.payload.pull_request.head.sha
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (statuses.find(status => status.context === 'PRChecklist') || {}).state
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrEditChecklist (context, checkList, header) {
|
||||||
|
const owner = context.payload.repository.owner.login
|
||||||
|
const repo = context.payload.repository.name
|
||||||
|
const number = context.payload.pull_request.number
|
||||||
|
if (checkList && checkList.length > 0) {
|
||||||
|
let body = '<!--prchecklist--> \n' + header + '\n'
|
||||||
|
for (const key of checkList) {
|
||||||
|
body += '- [ ] ' + key + '\n'
|
||||||
|
}
|
||||||
|
await context.github.issues.createComment({ owner, repo, number, body })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyChecklist (context, settings) {
|
||||||
|
let isChecklistComplete = true
|
||||||
|
let firstCheck = false
|
||||||
|
const {found, body} = await getCheckList(context)
|
||||||
|
if (found) {
|
||||||
|
if (body) {
|
||||||
|
for (const str of body) {
|
||||||
|
const res = str.match(/(-\s\[(\s)])(.*)/gm)
|
||||||
|
if (res != null) {
|
||||||
|
isChecklistComplete = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isChecklistComplete = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await createOrEditChecklist(context, settings.checklist, settings.title)
|
||||||
|
isChecklistComplete = false
|
||||||
|
firstCheck = true
|
||||||
|
}
|
||||||
|
return {isChecklistComplete, firstCheck}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCheckList (context) {
|
||||||
|
try {
|
||||||
|
const owner = context.payload.repository.owner.login
|
||||||
|
const repo = context.payload.repository.name
|
||||||
|
const number = context.payload.pull_request.number
|
||||||
|
const comments = await context.github.paginate(context.github.issues.getComments({ owner, repo, number }), res => res.data)
|
||||||
|
for (const comment of comments) {
|
||||||
|
const {found, body} = checkPRChecklist(comment.body)
|
||||||
|
if (found) {
|
||||||
|
return {found, body}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} catch (e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPRChecklist (str) {
|
||||||
|
let found = false
|
||||||
|
let body = null
|
||||||
|
const isBotComment = str.match(/(<!--prchecklist-->)/g)
|
||||||
|
if (isBotComment == null) return {found, body}
|
||||||
|
let res = str.match(/(-\s\[(\s|x)])(.*)/gm)
|
||||||
|
if (res && res.length > 0) {
|
||||||
|
found = true
|
||||||
|
body = res
|
||||||
|
}
|
||||||
|
return {found, body}
|
||||||
|
}
|
3
index.js
3
index.js
|
@ -38,7 +38,8 @@ module.exports = async (robot) => {
|
||||||
require('./bot_scripts/notify-reviewers-via-slack')(robot)
|
require('./bot_scripts/notify-reviewers-via-slack')(robot)
|
||||||
require('./bot_scripts/tip-kudos-recipients')(robot)
|
require('./bot_scripts/tip-kudos-recipients')(robot)
|
||||||
require('./bot_scripts/check-bot-balance')(robot)
|
require('./bot_scripts/check-bot-balance')(robot)
|
||||||
|
require('./bot_scripts/manage-pr-checklist')(robot)
|
||||||
|
|
||||||
// For more information on building apps:
|
// For more information on building apps:
|
||||||
// https://probot.github.io/docs/
|
// https://probot.github.io/docs/
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue