From 852a21cf28060a762e2c19c87b39282c188fc3fc Mon Sep 17 00:00:00 2001 From: Yordan Ivanov Date: Thu, 26 Oct 2017 08:31:16 +0300 Subject: [PATCH] Add pull request state badge --- lib/colorscheme.json | 3 +- lib/github-provider.js | 98 ++++++++++++++++++++++- server.js | 8 +- service-tests/github.js | 170 ++++++++++++++++++++++++++++++++++++++++ try.html | 7 ++ 5 files changed, 282 insertions(+), 4 deletions(-) diff --git a/lib/colorscheme.json b/lib/colorscheme.json index d4f4d43c9eadb..c66a706bc06ff 100644 --- a/lib/colorscheme.json +++ b/lib/colorscheme.json @@ -9,5 +9,6 @@ "grey": { "colorB": "#555" }, "gray": { "colorB": "#555" }, "lightgrey": { "colorB": "#9f9f9f" }, - "lightgray": { "colorB": "#9f9f9f" } + "lightgray": { "colorB": "#9f9f9f" }, + "purple": { "colorB": "#6f42c1" } } diff --git a/lib/github-provider.js b/lib/github-provider.js index da115907cda55..d546b4933b92a 100644 --- a/lib/github-provider.js +++ b/lib/github-provider.js @@ -120,8 +120,104 @@ function mapGithubReleaseDate(camp, githubApiUrl, githubAuth) { })); } +function mapGithubPRState(camp, githubApiUrl, githubAuth){ + camp.route(/^\/github\/pr-state\/([^/]+)\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, + cache((data, match, sendBadge, request) => { + const [, owner, repo, number, format] = match; + const apiUrl = `${githubApiUrl}/repos/${owner}/${repo}/pulls/${number}`; + const badgeData = getBadgeData('pull request', data); + + if (badgeData.template === 'social') { + badgeData.logo = getLogo('github', data); + } + + githubAuth.request(request, apiUrl, {}, function (err, res, buffer) { + if (err != null) { + badgeData.text[1] = 'inaccessible'; + sendBadge(format, badgeData); + return; + } + + //github return 404 if repo not found or no pr + if(res.statusCode === 404) { + badgeData.text[1] = 'no pr or repo not found'; + sendBadge(format, badgeData); + return; + } + + try { + const pr = JSON.parse(buffer); + let colorscheme = ''; + let status = ''; + + const responseIsIssueLike = pr.hasOwnProperty('state') && + pr.hasOwnProperty('merged') && + pr.hasOwnProperty('mergeable') && + pr.hasOwnProperty('mergeable_state'); + + if(!responseIsIssueLike) { + badgeData.text[1] = 'inaccessible'; + sendBadge(format, badgeData); + return; + } + + if (pr.state === 'closed') { + badgeData.text[1] = pr.merged ? 'merged' : 'closed'; + badgeData.colorscheme = pr.merged ? 'purple' : 'closed'; + sendBadge(format, badgeData); + return; + } + + if(pr.state !== 'open') { + badgeData.text[1] = 'inaccessible'; + sendBadge(format, badgeData); + return; + } + + switch (pr.mergeable_state) { + case 'clean': + status = 'mergeable'; + break; + case 'dirty': + status = 'has conflicts'; + break; + case 'behind': + status = 'needs rebase'; + break; + default: + status = pr.mergeable_state; + } + + switch (pr.mergeable) { + case null: + colorscheme = 'lightgrey'; + break; + case 'unstable': + case 'behind': + colorscheme = 'yellow'; + break; + case true: + colorscheme = 'green'; + break; + default: + colorscheme = 'red'; + } + + badgeData.text[1] = status; + badgeData.colorscheme = colorscheme; + sendBadge(format, badgeData); + } catch (e) { + console.log(e); + badgeData.text[1] = 'invalid'; + sendBadge(format, badgeData); + } + }); + })); +} + module.exports = { mapGithubCommitsSince, - mapGithubReleaseDate + mapGithubReleaseDate, + mapGithubPRState }; diff --git a/server.js b/server.js index c09f9d922c89b..d13bc2c2f1fd5 100644 --- a/server.js +++ b/server.js @@ -109,7 +109,8 @@ const { const { mapGithubCommitsSince, - mapGithubReleaseDate + mapGithubReleaseDate, + mapGithubPRState } = require("./lib/github-provider"); var semver = require('semver'); @@ -3433,7 +3434,10 @@ cache(function(data, match, sendBadge, request) { mapGithubReleaseDate(camp, githubApiUrl, githubAuth); // GitHub commits since integration. -mapGithubCommitsSince(camp, githubApiUrl ,githubAuth); +mapGithubCommitsSince(camp, githubApiUrl, githubAuth); + +// GitHub pull request state +mapGithubPRState(camp, githubApiUrl, githubAuth); // GitHub release-download-count and pre-release-download-count integration. camp.route(/^\/github\/(downloads|downloads-pre)\/([^/]+)\/([^/]+)(\/.+)?\/([^/]+)\.(svg|png|gif|jpg|json)$/, diff --git a/service-tests/github.js b/service-tests/github.js index 5b5186dc1c78a..4afe9eb1a0f8f 100644 --- a/service-tests/github.js +++ b/service-tests/github.js @@ -66,6 +66,176 @@ t.create('GitHub closed issues raw') value: Joi.string().regex(/^\w+\+?$/) })); +// pull request state + +t.create('Pull request state, regular case') + .get('/pr-state/badges/shields/578.json') + .expectJSONTypes(Joi.object().keys({ + name: Joi.equal('pull request state'), + value: Joi.equal('closed', 'merged', 'mergeable', 'has conflicts', 'need rebase') + })); + +t.create('Pull request state, closed pull request') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, { + mergeable: null, + mergeable_state: 'unknown', + merged: false, + state: 'closed' + }) + ) + .expectJSON({ + name: 'pull request state', + value: 'closed' + }); + +t.create('Pull request state, merged pull request') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, { + mergeable: null, + mergeable_state: 'unknown', + merged: true, + state: 'closed' + }) + ) + .expectJSON({ + name: 'pull request state', + value: 'merged' + }); + +t.create('Pull request state, pull request with unknown state') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, { + mergeable: null, + mergeable_state: 'unknown', + merged: false, + state: 'open' + }) + ) + .expectJSON({ + name: 'pull request state', + value: 'unknown' + }); + +t.create('Pull request state, mergeable pull request') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, { + mergeable: true, + mergeable_state: 'clean', + merged: false, + state: 'open' + }) + ) + .expectJSON({ + name: 'pull request state', + value: 'mergeable' + }); + +t.create('Pull request state, pull request with conflicts') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, { + mergeable: false, + mergeable_state: 'dirty', + merged: false, + state: 'open' + }) + ) + .expectJSON({ + name: 'pull request state', + value: 'has conflicts' + }); + +t.create('Pull request state, pull request that needs rebase') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, { + mergeable: false, + mergeable_state: 'behind', + merged: false, + state: 'open' + }) + ) + .expectJSON({ + name: 'pull request state', + value: 'need rebase' + }); + +t.create('Pull request state, mergeable pull request but build unstable') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, { + mergeable: true, + mergeable_state: 'unstable', + merged: false, + state: 'open' + }) + ) + .expectJSON({ + name: 'pull request state', + value: 'unstable' + }); + +t.create('Pull request state, valid response but not a Github issue') + .get('/pr-state/badges/shields/-1.json') + .expectJSONTypes(Joi.object().keys({ + name: Joi.equal('pull request state'), + value: Joi.equal('inaccessible') + })); + +t.create('Pull request state, invalid response') + .get('/pr-state/badges/shields/578.json') + .intercept(nock => + nock('https://api.github.com') + .get('/repos/badges/shields/pulls/578') + .query(true) + .reply(200, 'This should be JSON') + ) + .expectJSON({ + name: 'pull request state', + value: 'inaccessible' + }); + +t.create('Pull request state, request error') + .get('/pr-state/badges/shields/578.json') + .networkOff() + .expectJSON({ + name: 'pull request state', + value: 'inaccessible' + }); + +t.create('GitHub pull request state') + .get('/pr-state/badges/shields/6.json') + .expectedJSONTypes(Joi.object().keys({ + name: 'pull request', + value: Joi.string().regex(/^\w+/) + })); + t.create('GitHub open issues') .get('/issues/badges/shields.json') .expectJSONTypes(Joi.object().keys({ diff --git a/try.html b/try.html index 3e5d8662979e6..f6403b15f3b58 100644 --- a/try.html +++ b/try.html @@ -1577,6 +1577,13 @@

Miscellaneous

https://img.shields.io/github/issues-pr-closed/cdnjs/cdnjs.svg + + GitHub pull request state: + + + + https://img.shields.io/github/pr-state/badges/shields/6.svg + GitHub issues by-label: