Skip to content

Instantly share code, notes, and snippets.

@heybenchen
Created April 4, 2019 16:18
Show Gist options
  • Save heybenchen/c88e2cc14bc4f8cd2ca594fb9c66ad60 to your computer and use it in GitHub Desktop.
Save heybenchen/c88e2cc14bc4f8cd2ca594fb9c66ad60 to your computer and use it in GitHub Desktop.
PR Review Bot
var express = require('express');
var Webtask = require('webtask-tools');
var bodyParser = require('body-parser');
var urlParse = require('url-parse');
var rp = require('request-promise');
var app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.post('/event', function(req, res) {
if (req.body.challenge) { return res.json({challenge: req.body.challenge}); }
var prUrl = req.body.event.links[0].url;
return fetchGithubPrAndRespond(req.webtaskContext.secrets, prUrl, res);
// TODO: To make this work, we have to return a response within 3 seconds.
// Let's return a button to convert to Github link instead of trying to fetch PR right away.
});
app.post('/create', function(req, res) {
var body = req.body;
if (!verifyToken(body.token, req)) { return res.status(401); }
var prUrl = body.text;
return fetchGithubPrAndRespond(req.webtaskContext.secrets, prUrl, res);
});
app.post('/update', function(req, res) {
var parsedPayload = JSON.parse(req.body.payload);
if (!verifyToken(parsedPayload.token, req)) { return res.status(401); }
var action = parsedPayload.actions[0].name;
var prUrl = parsedPayload.actions[0].value;
switch (action) {
case 'refresh':
return fetchGithubPrAndRespond(req.webtaskContext.secrets, prUrl, res, false);
case 'bump':
return fetchGithubPrAndRespond(req.webtaskContext.secrets, prUrl, res, true);
default:
return res.status(500).json({error: "Invalid action type"});
}
});
function verifyToken(token, req) {
return token === req.webtaskContext.secrets.SLACK_TOKEN;
}
function parsePRUrl(url) {
var parsedUrl = urlParse(url);
if (parsedUrl.host === 'github.com') {
return parsePathname(parsedUrl.pathname);
}
return null;
}
function parsePathname(pathname) {
var sections = pathname.split('/');
return {
org: sections[1],
repo: sections[2],
pr: sections[4],
};
// TODO: input validation
}
function fetchGithubPrAndRespond(secrets, prUrl, res, isBump) {
var gUrl = parsePRUrl(prUrl);
fetchGithubPr(secrets, gUrl.org, gUrl.repo, gUrl.pr)
.then(response => {
var parsed = JSON.parse(response);
res.json(buildPrResponse(parsed, isBump));
}).catch(error => {
console.log('ERROR: ' + error);
res.status(500).json({error: error});
});
}
function fetchGithubPr(secrets, org, repo, pr) {
var username = secrets.GITHUB_USERNAME;
var password = secrets.GITHUB_PASSWORD;
var githubApiUrl = "https://" + username + ":" + password + "@api.github.com/repos/" + org + "/" + repo + "/pulls/" + pr;
var options = {
url: githubApiUrl,
headers: {
'User-Agent': 'GetReview'
}
};
return rp(options);
}
function buildPrResponse(parsedResponse, isBump) {
var title = parsedResponse.title + " [+" + parsedResponse.additions + "/-" + parsedResponse.deletions + "]";
var text = truncateText(parsedResponse.body, 250);
return {
replace_original: isBump ? false : true,
response_type: "in_channel",
text: isBump ? "*Bump:*" : "",
mrkdwn: true,
attachments: [
{
author_name: parsedResponse.user.login,
author_link: parsedResponse.user.url,
author_icon: parsedResponse.user.avatar_url,
title: title,
title_link: parsedResponse.html_url,
text: text,
callback_id: "update",
fallback: "Review this PR",
attachment_type: "default",
actions: [
{
name: "refresh",
text: "Refresh",
type: "button",
value: parsedResponse.html_url
},
{
name: "bump",
text: "Bump",
type: "button",
style: "danger",
value: parsedResponse.html_url
}
]
}
]
};
}
function truncateText(text, limit) {
return text.substring(0, limit) + "...";
}
module.exports = Webtask.fromExpress(app);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment