Github Appでレビュー済みPRに自動的にラベルを追加

# 概要
GithubのPRがレビュー済みになった際にラベルを付けておくと、レビュー済みPRが見分けやすくて便利です。
今回その作業をGithub Appで自動化することにしました。
以下はその作業ログです。

# Github Appの作成
## 作成するAppの仕様
今回作成するAppは
`すべてのreviewerがApproveしたらreviewedラベルをPRに追加する`
という仕様にします。

## Probot
ProbotはGithub Appのframeworkです。
ドキュメントも充実していて、Github Appの作成を効率良く行えるので、今回はこれを使うことにしました。

## Node.js
Probotを使うにはNode.jsが必要になります。
[このページ](https://probot.github.io/docs/development/)を参照して、動作に必要なバージョンをインストールしておきます。

## 雛形の作成
yarnとnpmを使う方法がドキュメントにはありますが、今回はnpmを使っていきます。

下記コマンドを実行すると、Probotが雛形を作ってくれます。

```
npx create-probot-app {app-name}
```

対話形式でApp nameなどを聞かれるので答えていきます。

## Gihub repositoryへのpush
ここまでのソースをGithubのrepositoryへpushしておきます。

## smee.io
ローカルでの開発時にもGithubのwebhookを受け取れるようにするため、
[smee.io](https://smee.io/)を利用します。
`Start a new channel`ボタンを押すと、`Webhook Proxy URL`が生成されるので、
このURLとローカルのappを結びつけて開発していきます。
Probotはsmee.ioと簡単に連携できるようになっています。

## Github Appの作成
https://github.com/settings/apps/new
から新規Appを作成します。

`Homepage URL`にはgithub repositoryのURLを、
`Webhook URL`にはsmee.ioで生成したURLを、
`Webhook secret (optional)`にはひとまず`development`をいれておきます。(あとで書き換えます)

`Permissions`はGithub Appでアクセスできる権限を指定します。
今回作成するAppはPull Requestのreviewの内容をみて、labelを追加するので
`Pull requests`に`Read-only`,labelを操作するのはIssueのpermissionのようなので
`Issue`に`Read & Write`を設定します。
それ以外はデフォルトのままにしておきます。

`Subscribe to events`は`Approve`されたことの通知を受けるために`Pull request review`を、それとPRのheadブランチ更新のタイミングで`Approve`が無効になることがあるので、それも検知するために`Pull request`もsubscribeしておきます。

`Where can this GitHub App be installed?`
 はAppの公開範囲をどこまでにするのかの設定ですが、一般に公開されて問題なければ
`Any account`にしておきます。

設定が終わったら`Create Github App`ボタンを押してAppを作成します。

## private keyの生成
Appの作成後の画面で、`Generate a private key`ボタンがあるので、
ボタンを押してprivate keyを発行しておきます。
このkeyはAppの実行に必要になります。
発行すると自動的にkeyがダウンロードされるので、レポジトリ直下に移動しておきます。
(既に.gitignoreで*.pemが指定されているので、git管理外になります。)

## .envの設定
`.env.example`をコピーして`.env`を作ります。

.env内のAPP_IDとWEBHOOK_PROXY_URLを設定します。
APP_IDは`https://github.com/settings/apps/{app-name}`ページ内に書かれている`ID`の値で、`WEBHOOK_PROXY_URL`にはsmee.ioのURLをいれておきます。


## Appの起動
`npm run dev`で起動できることを確認します。

## webhookの取得
今回作成する　AppはPRがApproveされたタイミングで、全てのreviewerがApproveしたかをチェックします。
そのため、reviewがsubmitされたタイミングの通知を監視します。

```
app.on('pull_request_review.submitted', async context => {
  app.log('submitted')
  }
```

## Approve回数の取得
Approveされたreviewを取得するために、reviewのリストを取得します。

```
const params = context.issue()
const reviews = await context.github.pullRequests.getReviews(params)
```
`context.github`は[ocotokit/rest](https://octokit.github.io/rest.js/)のインスタンスで、これを使うと簡単にGithub APIにアクセスできます。

reviewのstatusが`APPROVED`になっているものがApproveされた際のreviewなので、この回数をカウントしておきます。

## reviewerの取得
reviewerを直接取得するAPIは存在しないようなので、reviewerに指名されてまだ一度もreviewしていないuserを取得する`getReviewRequests`の数と、reviewしたすべてのuser数を組み合わせることでreviewerの総数とします。

PRを作成したuserもreviewはできるので、PRの作成者はカウントしないようにしておきます。
PRの作成者は

```
const params = context.issue()
const pull_request = await context.github.pullRequests.get(params)
const creator = pullRequest.data.user.login
```

として取得できます。

## ラベルの追加
`reviewerの数==Approveの数`になったらラベルを追加します。
ラベル名はひとまず`reviewed`としておきます。

ラベルの追加は

```
const labels = context.issue({ labels: ['reviewed'] })
context.github.issues.addLabels(labels)
```

で行います。
labelsの配列に複数ラベルを指定すれば、一度にまとめてラベルを追加することもできます。

## ラベルの削除
Githubのレポジトリの設定で`Dismiss stale pull request approvals when new commits are pushed`にチェックがついていると、reviewがApproveされたあと、再度該当branchにpushが走ると、そのタイミングでApproveが無効になります。

その際は再度Approveしてもらう必要があるので、ラベルも取り除いておきます。
PRの対象branch更新のeventは　`pull_request.synchronize`を監視することで取得できます。
この際に`reviewer数 !== Approve数` になっていて、かつ`reviewed`ラベルがついていたらラベルの削除APIを投げておきます。

```
const rmLabel = context.issue({ name: 'reviewed' })
context.github.issues.removeLabel(rmLabel)
```

# ラベル名の変更
ここまではラベルを`reviewed`固定にしていましたが、設定で変えられるようにしておきます。

このGithub Appをインストールしたレポジトリに`.github/reviewed.yml`があった場合、そこに書かれているラベルを使用し、なければデフォルトの`reviewed`ラベルを使うようにします。

```.github/reviewed.yml
labelName: ok
```

`probot-config`を使うと簡単にymlにアクセスできるので、package.jsonに追加しておきます。

```
npm install -S probot-config
```

probot-configを使ってreviewed.ymlを取得します。

```
const getConfig = require('probot-config')

const defaultConfig = {
  labelName: 'reviewed'
}
const config = await getConfig(context, 'reviewed.yml') || defaultConfig
const label = config.labelName
```

## deploy
Probotのdocumentにはdeploy先として、Glitch,Heroku,Nowが紹介されていますが、今回はNowを使用します。

https://probot.github.io/docs/deployment/#now
上記URLにdeployの手順が書いてありますが、この手順ではエラーが出てしまったので、
https://github.com/probot/probot/blob/master/docs/deployment.md#now
側の手順を実行します。

違いはprivate_keyをbase64エンコードしてNowに送っている箇所です。
なお、デプロイの度に生成されるURLが変わるみたいなので、毎回aliasを付け直す必要があるようです。

## Webhook APIの書き換え
Nowでaliasを振ったAPIのURLでGithub Appの`Webhook URL`を変更します。
併せて`Webhook secret`も変更しておくと安全です。
`Webhook secret`を変更した際には、再度Nowのdeployが必要になります。

## repository & app
今回作成したAppのレポジトリは[こちら](https://github.com/g-ozawa/reviewed)になります。
Appのリンクは[こちら](https://github.com/apps/reviewed)になります。

気に入って頂けたらInstallして使ってみてもらえると嬉しいです。