【Github Actions】PRのレビュアーにチームのメンバーを自動で設定する

## はじめに

私の所属しているチームでは、プルリクエストを作成したら
複数人でコードをレビューする運用を行っています。

運用を始めた頃は、チームメンバーを全員を含めた[Team](https://docs.github.com/ja/organizations/organizing-members-into-teams/about-teams)を作成し、
手動でTeamをレビュアーに設定していましたが、以下のような問題がありました。

* Teamのうち一人がapproveすると、レビュアーからTeamが消えてしまう。他のメンバーにもレビューしてもらいたい場合、再度レビュアーを設定する必要がある。（手間だし、忘れてしまうことも:sweat:）
* そもそもレビュアーを設定し忘れる:sweat_drops:

そこで、GitHub Actionsを使って、自動でレビュアーをアサインする仕組みを導入しました。

## 実現したいこと

この記事で実現したいことを簡単にまとめると、以下の通りです。

* プルリクを作成したら、チームのメンバー全員を、自動でレビュアーに指定する。
* Teamのメンバーを変更した場合も、設定ファイルを書き換えずに済むと嬉しい。
* ドラフト作成時にはレビュアーを設定しない。
* レビュー対象はmasterブランチのみ。

レビュアーを自動でアサインする仕組みはいくつかありますが、
上記の要件と合わなかったため、自前のGitHub Actionsを設定することにしました。

## 事前準備

### 個人アクセストークンの取得
このページで解説するGitHub Actionsの実行には、admin:org権限をもった個人アクセストークンが必要です。

[公式ドキュメント](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)を参考に、トークンを取得します。

>このトークンに付与するスコープ、すなわち権限を選択します。 トークンを使用してコマンドラインからリポジトリにアクセスするには、[repo] を選択します。

ここでは、以下の権限にチェックを入れます。
![スクリーンショット 2022-01-27 16.37.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311681/a55fd7ba-da44-9920-68fc-510f7289f896.png)

トークンを取得できたら、リポジトリの[Secrets](https://docs.github.com/ja/actions/security-guides/encrypted-secrets)に登録しておきます。

### slack通知設定

このページで解説するGitHub Actionsは、GitHubのSlack通知と組み合わせるととても便利です:+1:

設定がまだの場合は、設定しておくことを強くオススメします。
[GitHub と Slack を連携させる](https://slack.com/intl/ja-jp/help/articles/232289568-GitHub-%E3%81%A8-Slack-%E3%82%92%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%82%8B)

連携していれば、レビュアーに設定されたら、slackで通知が飛んでくるようになります:raised_hands:


## GitHub Actionsの設定

以下のymlファイルを、リポジトリの`.github/workflows/`配下に設置します。

TODOコメントが入っているところは、任意の値に変更してください。
ファイルの内容については、次で詳しく解説していきます。

```pr-reviewer-assign.yml
name: PR Reviewer Auto Assignment

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
    branches: 
      - master
      
jobs:
  reviewer-assign:
    runs-on: ubuntu-latest
    env:
      API_BASE_URL: "https://api.github.com"
      COMMON_HEADER: "Accept: application/vnd.github.v3+json"
      ORG: "ORG" # TODO:任意の値に変更する
      TEAM_SLUG: "TEAM_NAME"　 # TODO:任意の値に変更する
    
    if: github.event.pull_request.base.ref == 'master' && github.event.pull_request.draft == false
    steps: 
      - name: Get Members List
        id: get-members-list
        run: |
          response=$(curl -X GET \
               -H "$COMMON_HEADER" \
               -H "Authorization: token ${{ secrets.ORG_TOKEN }}" \　# TODO:任意の値に変更する
               $API_BASE_URL/orgs/$ORG/teams/$TEAM_SLUG/members)
          members=$(echo $response | jq '[.[].login | select(. != "${{ github.actor }}")]')
          echo ::set-output name=members::$members
          
      - name: Set Reviewers
        id: set-reviewers
        run: |
          curl -X POST \
               -H "$COMMON_HEADER" \
               -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
               $API_BASE_URL/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/requested_reviewers \
               -d '{ "reviewers": ${{ steps.get-members-list.outputs.members }} }'
 # 上記処理が失敗した場合、slack通知    
  error-notifications:
    if: failure()
    needs: reviewer-assign
    uses: REPO/.github/workflows/error-notifications.yml@master # TODO:任意の値に変更する
    with: 
      detail: "レビュアーのアサインに失敗しました。"
```


### トリガーの設定
`on`パラメータに`pull_request`を指定しています。
```pr-reviewer-assign.yml
on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
    branches: 
      - master

```

`types`には以下のものを指定することができ、何も指定しない状態（デフォルト）では★のついたトリガーが指定されています。

* assigned
* unassigned
* labeled
* unlabeled
* opened★
* edited
* closed
* reopened★
* synchronize★
* converted_to_draft
* ready_for_review
* locked
* unlocked
* review_requested
* review_request_removed
* auto_merge_enabled
* auto_merge_disabled

今回は、ドラフト作成後、Ready For Reviewに変更した場合もトリガーに含めたかったので、
デフォルトではなく明示的に指定しました。

使いまわすパラメータは環境変数に設定しました。

```pr-reviewer-assign.yml
jobs:
  reviewer-assign:
    runs-on: ubuntu-latest
    env:
      API_BASE_URL: "https://api.github.com"
      COMMON_HEADER: "Accept: application/vnd.github.v3+json"
      ORG: "ORG" # TODO:任意の値に変更する
      TEAM_SLUG: "TEAM_NAME"　 # TODO:任意の値に変更する
```


TODOコメントがついているところは、任意の値に変更してください。



ここでは二つの条件を指定しています。
二つの条件をクリアすると、以降のステップが実行されます。
```pr-reviewer-assign.yml
if: github.event.pull_request.base.ref == 'master' && github.event.pull_request.draft == false
```


一つ目の条件は、`baseブランチがmasterであること`です。
自分はbaseブランチがどっちかよくわからなくなってしまうのですが、PRの向き先がbaseブランチです。
![github-base-branch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311681/8b65896d-beae-7a1d-7c9d-d19637df052e.png)

二つ目の条件は、`draftではないこと`です。
これを指定することで、ドラフト作成時にレビュアーがアサインされるのを防いでいます。

### チームメンバーの取得

```pr-reviewer-assign.yml
steps: 
      - name: Get Members List
        id: get-members-list
        run: |
          response=$(curl -X GET \
               -H "$COMMON_HEADER" \
               -H "Authorization: token ${{ secrets.ORG_TOKEN }}" \　 # TODO:任意の値に変更する
               $API_BASE_URL/orgs/$ORG/teams/$TEAM_SLUG/members)
          members=$(echo $response | jq '[.[].login | select(. != "${{ github.actor }}")]')
          echo ::set-output name=members::$members
```



一つ目のステップでは、Teamに設定されているメンバーを以下のAPIで取得しています。

https://docs.github.com/ja/rest/reference/teams#list-team-members

チームメンバーの取得は、GITHUB_TOKENでは権限が足りないため、「事前準備」に記載の個人アクセストークンを使用します。
`secrets.ORG_TOKEN`はSecretsに登録した際の名前に変更してください。


jq部分がちょっとわかりにくいので解説すると、
responseを配列で扱うために、フィルターを[]で囲んでいます。

```
jq '[ /*example*/ ]'
```

本人（github.actor）はレビュアーに指定できないので、ここで除外しておきます。

```
select(. != "${{ github.actor }}")
```

```
echo ::set-output name=members::$members
```
ここでは何をしているかというと、次のステップから参照できるように、変数`members`を設定しています。

他のステップから参照する場合は次のように書きます。

```
# 書き方
${{ steps.<step-id>.outputs.<変数名> }}
 
# 例
${{ steps.get-members-list.outputs.members }}
```

### レビュアーのアサイン

```pr-reviewer-assign.yml
      - name: Set Reviewers
        id: set-reviewers
        run: |
          curl -X POST \
               -H "$COMMON_HEADER" \
               -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
               $API_BASE_URL/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/requested_reviewers \
               -d '{ "reviewers": ${{ steps.get-members-list.outputs.members }} }'
```

二つ目のステップでは、一つ目のステップで取得したメンバーをレビュアーにアサインしています。

https://docs.github.com/en/rest/reference/pulls#request-reviewers-for-a-pull-request

### 【おまけ】レビュアーのアサインに失敗した場合

アサインに失敗した場合は、チームのslackに通知するように設定しました。
```
 # 上記処理が失敗した場合、slack通知    
  error-notifications:
    if: failure()
    needs: reviewer-assign
    uses: REPO/.github/workflows/error-notifications.yml@master # TODO:任意の値に変更する
    with: 
      detail: "レビュアーのアサインに失敗しました。"
```

slack通知については、[【Github Actions】reusable workflowを使って、エラー時にslack通知する](https://qiita.com/suuu/items/871db22617300c4b061f)
で解説しています。

## さいごに

GitHub Actionsを使ったレビュアー自動設定について紹介しました。

実際にこの設定を導入したところ、チーム内でのプルリク作成→レビュー依頼が、スムーズにできるようになったと感じています。


最後までお読みいただき、ありがとうございました:bow: