- はじめに -
GithubのPull Requestを大体1日以内に処理するルールだったのだが、Repositoryが増えて全然管理できなくなったりしたので、Reviewerに入っていてApprovedしてないものだけSlackに通知しようとこねくり回したGoogle Apps Script。
Lambdaとかを使う方が楽だけど、GASは金が掛からないしポチポチで時間トリガーやSlack Bot化を進められるのが良さ。
GASでSlack botは前に書いたので、Slack投稿までは下記で出来ている前提。
- GithubのPersonal Access Tokenの取得 -
Githubをブラウザから見て、SettingsからAccessTokenを取得する。
右上のアイコンからAccount -> Settings
上記画像の赤枠をポチポチすると取得できるので、文字列を控えておく。
- GAS -
メインとなるGithub周りのスクリプトを導入する前に日付のフォーマット関連の処理をよしなに処理出来るようにしておく(GASではdatetimeの文字列処理が面倒なため)。
日付周りの処理
新しいスクリプトを追加してそれぞれファイル分けできるのでやる
新しく作ったスクリプトファイルにisodate関数を作っておく。
isodateとして以下コードを引用。
iso8601.js/iso8601.js at master · shumpei/iso8601.js · GitHub
/* * JavaScript library for ISO-8601 datetime format. * Copyright: 2009, Shumpei Shiraishi (shumpei.shiraishi at gmail.com) * License: GNU General Public License, Free Software Foundation * <http://creativecommons.org/licenses/GPL/2.0/> * Original code and license is: * Web Forms 2.0 Cross-browser Implementation <http://code.google.com/p/webforms2/> * Copyright: 2007, Weston Ruter <http://weston.ruter.net/> * License: GNU General Public License, Free Software Foundation * <http://creativecommons.org/licenses/GPL/2.0/> */ var isodate=function(){function e(e,t){t||(t=2);for(var r=e.toString();r.length<t;)r="0"+r;return r}var t=/^(?:(\d\d\d\d)-(W(0[1-9]|[1-4]\d|5[0-2])|(0\d|1[0-2])(-(0\d|[1-2]\d|3[0-1])(T(0\d|1\d|2[0-4]):([0-5]\d)(:([0-5]\d)(\.(\d+))?)?(Z)?)?)?)|(0\d|1\d|2[0-4]):([0-5]\d)(:([0-5]\d)(\.(\d+))?)?)$/;return{validate:function(e,r){var n=!1,a=t.exec(e);if(!a||!r)return a;if(r=r.toLowerCase(),"week"==r)n=0===a[2].toString().indexOf("W");else if("time"==r)n=!!a[15];else if("month"==r)n=!a[5];else if(a[6]){var s=new Date(a[1],a[4]-1,a[6]);if(s.getMonth()!=a[4]-1)n=!1;else switch(r){case"date":n=a[4]&&!a[7];break;case"datetime":n=!!a[14];break;case"datetime-local":n=a[7]&&!a[14]}}return n?a:null},parse:function(e,t){if(!e)return null;var r=this.validate(e,t);if(!r)return null;var n=new Date(0),a=8;if(r[15]){if(t&&"time"!=t)return null;a=15}else{if(n.setUTCFullYear(r[1]),r[3])return t&&"week"!=t?null:(n.setUTCDate(n.getUTCDate()-(7-n.getUTCDay())+7*(r[3]-1)),n);n.setUTCMonth(r[4]-1),r[6]&&n.setUTCDate(r[6])}return r[a+0]&&n.setUTCHours(r[a+0]),r[a+1]&&n.setUTCMinutes(r[a+1]),r[a+2]&&n.setUTCSeconds(r[a+3]),r[a+4]&&n.setUTCMilliseconds(Math.round(1e3*Number(r[a+4]))),r[4]&&r[a+0]&&!r[a+6]&&n.setUTCMinutes(n.getUTCMinutes()+n.getTimezoneOffset()),n},format:function(t,r){if(!t)return null;r=String(r).toLowerCase();var n="";switch(t.getUTCMilliseconds()&&(n="."+e(t.getUTCMilliseconds(),3).replace(/0+$/,"")),r){case"date":return t.getUTCFullYear()+"-"+e(t.getUTCMonth()+1)+"-"+e(t.getUTCDate());case"datetime-local":return t.getFullYear()+"-"+e(t.getMonth()+1)+"-"+e(t.getDate())+"T"+e(t.getHours())+":"+e(t.getMinutes())+":"+e(t.getSeconds())+n+"Z";case"month":return t.getUTCFullYear()+"-"+e(t.getUTCMonth()+1);case"week":var a=this.parse(t.getUTCFullYear()+"-W01");return t.getUTCFullYear()+"-W"+e(Math.floor((t.valueOf()-a.valueOf())/6048e5)+1);case"time":return e(t.getUTCHours())+":"+e(t.getUTCMinutes())+":"+e(t.getUTCSeconds())+n;case"datetime":default:return t.getUTCFullYear()+"-"+e(t.getUTCMonth()+1)+"-"+e(t.getUTCDate())+"T"+e(t.getUTCHours())+":"+e(t.getUTCMinutes())+":"+e(t.getUTCSeconds())+n+"Z"}}}}();
Githubからの情報取得
以下メインのスクリプト
userにReviewerかどうか知りたいユーザ(つまり自分)、TeamはそのRepository製作者やチーム名、githubAccessTokenに上記で取得したアクセストークンを入れる。
pullsNameListにはチェックしたいRepositoryの名前をリストで入れておく感じ。
// GithubのプルリクをチェックしてApprovedしてないやつを確認する // 各設定 var user = "testersp"; var team = "vaaaaanquish"; var githubAccessToken = "hogehoge"; var baseUrl = "https://api.github.com/repos/" + team + "/{}/pulls"; var pullsNameList = ["pull-request-test"]; // pull requestsのURLを生成 var pullsUrlList = []; for (var i = 0; i < pullsNameList.length; i++) { pullsUrlList.push(baseUrl.replace("{}", pullsNameList[i])); } // Httpオプションとヘッダ var httpOptions = { method: "GET", }; var httpOptionsReviews = { method: "GET", headers: {"Accept":"application/vnd.github.black-cat-preview+json"} }; // main function github_main() { // 土日は実行しない var today = new Date(); if (today.getDay() == 0 || today.getDay() == 6) return; // GithubAPIを走査しSlack用の文字列allContentsを生成する var allContents = ''; for (var i = 0; i < pullsUrlList.length; i++) { allContents += doOneRepository(pullsUrlList[i]); } return allContents; } // 1つのリポジトリから情報習得 var doOneRepository = function(githubUrl) { // リポジトリの基本情報を習得(プルリク取得) var response = getResponse(githubUrl, httpOptions); if (response === null || response.length == 0) return ""; // 1リポジトリ分の文字列を生成 var allContents = ''; for (var i = 0; i < response.length; i++) { var r = response[i]; var createdAt = isodate.parse(r["created_at"]); var info = {}; info.prNumber = r["number"]; info.prTitle = r["title"]; info.owner = r["user"]["login"]; info.hourCreate = r["created_at"]; // requested_reviewersに入っているユーザ // (recviewersに入っていてCommentもApproveもしていない) info.notApprovedPeople = getNotApproved(githubUrl+"/"+r["number"]+"/requested_reviewers"); // 文字列生成 var content = buildSlackPostingString(info); if(content.length > 1){ allContents += (content + "\n"); } } // 文字列を整形して返す allContents.replace(/.+\n$/g,"") var repository = githubUrl.split("/")[5]; if(allContents.length > 1){ return repository + '\n' + allContents + '\n'; }else{ return ""; } } // requested_reviewersに入っている人を返す var getNotApproved = function (GithubUrlReviews) { var res = getResponse(GithubUrlReviews, httpOptionsReviews); var result = []; for (var i = 0; i<res.length; i++){ result.push(res[i]["login"]); } Logger.log(result); return result; } // HTTP GETリクエストを投げて返却値をJSONとして得る var getResponse = function (url, options) { var urlToken = url + "?access_token=" + githubAccessToken; var response = UrlFetchApp.fetch(urlToken, options); if (response.getResponseCode() != 200) return null; return JSON.parse(response.getContentText()); }; // Slackに投稿する文字列の生成 var buildSlackPostingString = function (info) { if(info.notApprovedPeople.indexOf(user) > -1 ){ var content = '> • #' + info.prNumber + ' ' + info.prTitle; content += ' - @' + info.owner; content += '\n> > CREATED: *' + info.hourCreate.split("T")[0] + '*'; return content; }else{ return ""; } }
具体的にはRepositoryを走査して、requested_reviewersエッジの情報に自分が入っているかをチェックするというもの。
土日は仕事をすべきではないので通知しないようにしている。
requested_reviewers自体はまだPreviewなので注意。以下Reference。
Review Requests | GitHub Developer Guide
以下のような状態でのみ反応する。
CommentかApproveするとrequested_reviewersには入って来ない。
この結果を冒頭に書いた記事内にある、GASとSlackの連携を行ったスクリプトに導入する。
Slack botをGASでつくる方法で一番楽そうなやつ - Stimulator
function postSlack(text){ var url = "https://hooks.slack.com/services/hogehogehoge~"; var options = { "method" : "POST", "headers": {"Content-type": "application/json"}, "payload" : '{"text":"' + text + '"}' }; UrlFetchApp.fetch(url, options); } function test(){ s = github_main(); postSlack("未読プルリク情報\n" + s); }
以下のようにSlackに通知される。
GASの設定で適当に1日数回、時間トリガーで発火するようにしておけば大体大丈夫。
もしくはSlackのbotにして応答させれば良い。
- おわりに -
かつてrequested_reviewersが動かなかった頃は、review_comments_urlからcommentをそれぞれ見に行ってcommentしているかチェックしたりしていたんですが、Reviewer機能が実装されたことでこの辺のチェックも本当に簡単になりました。
良かったです。
GAS便利だけどちょっと書くのがしんどいです。
GASエディタもStylishのCSS書き換えで少し見やすくしているけど、それでもやっぱりローカル開発環境で十二分に色々やりたいという気持ちになってしまう。
Stylish - ウェブサイト用カスタムテーマ - Chrome ウェブストア
Google Apps Script Dark - Monokai | Userstyles.org
出来ることなら使わずに金を払って既存サービスに任せたい所です。
本末転倒ですが以上です。