NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    残っているプルリクエストをレビュワーにメンションつきでSlack通知してみた

    クラウド事業推進部の石倉です。

    プルリクエスト(以降PR)、溜まったまま忘れていませんか?
    自分がレビュワーになっているPRをついつい忘れてしまう、そんな時ありますよね?
    今回は残っているPRをそのレビュワーに向けてメンションつきでSlackに通知してみました。

    例えばこのようなPRの状態の場合、

    以下のような形で送られてきます。
    (環境の都合上、今回は担当者をレビュワーと仮定しています)

    準備

    今回Slackへ通知を行うにあたりSlackのWebhookURLを使用します。
    Slackappを作成してWebhookURLを用意しましょう。

    api.slack.com

    WebhookURLを用意したらGitHubのシークレットに保存しておきます。

    まずはGitHubActionsとSlackを連携しよう

    今回はSlackが提供しているGitHubActionを利用してSlackに通知を送ります。

    github.com

    このページの記載の中で、今回はWebhookURLを使った Technique 3: Slack incoming webhook の方法で行います。
    上記を参考にGitHubActionsのコードは以下のようにしてみました。

    name: PR Notification to Slack
    
    on: workflow_dispatch
    
    jobs:
      notify:
        runs-on: ubuntu-latest
        steps:
          - name: Post a message in a channel
            uses: slackapi/slack-github-action@v2.0.0
            with:
              webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
              webhook-type: incoming-webhook
              payload: |
                text: "ちゃんと通知届いてるかい?"

    手動でワークフローを実行してみます。

    無事メッセージが送られてきました。

    PRの情報を取得しよう

    今回はGitHubCLIというものを利用してPRの情報を取得します。

    docs.github.com

    gh pr list でPRの情報を取ることができるのでコードを追加してやってみましょう。

    name: PR Notification to Slack
    
    on: workflow_dispatch
    
    jobs:
      notify:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Get Information
            run: |
              gh pr list --json title,assignees,url
    
          - name: Post a message in a channel
            uses: slackapi/slack-github-action@v2.0.0
            with:
              webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
              webhook-type: incoming-webhook
              payload: |
                text: "ちゃんと通知届いてるかい?"

    環境の都合上、今回はassigneesの情報をレビュワーと仮定して進めています。本来の目的通りレビュワーの情報でやりたい場合はassigneesをreviewRequestsに変えればレビュワーの情報が取れます。

    実行してみましょう。

    エラーになりました。
    トークンの設定が必要のようなのでエラーに書いてある通り追加します。

    name: PR Notification to Slack
    
    on: workflow_dispatch
    
    jobs:
      notify:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Get Information
            run: |
              gh pr list --json title,assignees,url
            env:
              GH_TOKEN: ${{ github.token }}
    
          ...(略)

    再度実行。

    またエラーになりますね。
    これはトークンの権限が足りないようです。ワークフロー内での権限調整もできたりするようですが今回はコンソールからリポジトリに対しての権限を緩めて対応します。

    docs.github.com

    Settingsタブ > Actions > General からWorkflow permissionsの設定を以下のように変更します。

    これで実行してみます。

    成功しました。 以下のようなPRの情報が取れているかと思います。

    [{"assignees":[{"id":"xxxxx","login":"アカウント名","name":"プロフィール名"}],"title":"PRのタイトル","url":"PRのURL"}]

    ちなみに本来のreviewRequestsの場合はこのような形になります。

    [{"reviewRequests":[{"__typename":"User","login":"アカウント名"}],"title":"PRのタイトル","url":"PRのURL"}]

    これでPRの情報が取得できるようになりました。

    メンバーのGitHubアカウントの情報とSlackアカウントの情報の対応づけデータを用意しよう

    Slackにメンションをつけて送るためにSlackのメンバーIDが必要になります。メンバーIDは自身のプロフィール画面の「︙」から確認できます。

    今回はGitHubのアカウント名とSlackのメンバーIDを対応づけることにします。
    対応づけデータはjsonで用意することにしました。以下のようなイメージです。

    {
      "GitHubのアカウント名": "SlackのメンバーID",
      "GitHubのアカウント名": "SlackのメンバーID",
      ...
    }

    次にこのデータもWebhookURLと同様にシークレットに保存して参照する形にしようと思いました。

    が、jsonなどのデータはシークレットに入れないことがドキュメントに推奨されていました。

    docs.github.com

    ドキュメントを読むとBase64エンコードしてからシークレットに保存して、使用するときにデコードする方法が紹介されていたので今回はその方法を利用することにしました。

    # ローカルの端末で対応づけjsonデータをBase64エンコード
    base64 -i {jsonデータファイル} -o {出力先}
    # cat {出力先} で確認
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    エンコードされた値をシークレットに保存してください。

    試しに確認してみましょう。コードは以下です。

    name: PR Notification to Slack
    
    on: workflow_dispatch
    
    jobs:
      notify:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Get Information
            run: |
              gh pr list --json title,assignees,url
              echo "${{ secrets.MEMBER_DATA }}" | base64 --decode
            env:
              GH_TOKEN: ${{ github.token }}
    
          ...(略)

    実行するとデコードされて元々のjsonデータが見れるかと思います。

    通知するメッセージを作ろう

    Slackへの通知、PRの情報取得、GitHubアカウントの情報とSlackアカウントの情報の対応づけデータの準備ができたので、メッセージを作成していきます。

    ここまでの作業を終えた状態のコードは以下です。

    name: PR Notification to Slack
    
    on: workflow_dispatch
    
    jobs:
      notify:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Get Information
            run: |
              gh pr list --json title,assignees,url
              echo "${{ secrets.MEMBER_DATA }}" | base64 --decode
            env:
              GH_TOKEN: ${{ github.token }}
    
          - name: Post a message in a channel
            uses: slackapi/slack-github-action@v2.0.0
            with:
              webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
              webhook-type: incoming-webhook
              payload: |
                text: "ちゃんと通知届いてるかい?"

    メッセージを作成する処理を加えたコードが以下です。

    name: PR Notification to Slack
    
    on: workflow_dispatch
    
    jobs:
      notify:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Get Information
            run: |
              gh pr list --json title,assignees,url > pr.json
              echo "${{ secrets.MEMBER_DATA }}" | base64 --decode > member-data.json
            env:
              GH_TOKEN: ${{ github.token }}
    
          - name: Create Message
            id: create_message
            run: |
              echo "*PR残ってるから確認してね!*\\n" >> message
    
              PRS=$(cat pr.json)
              MEMBERS=$(cat member-data.json)
              echo "$PRS" | jq -c '.[]' | while read pr; do
                TITLE=$(echo "$pr" | jq -r '.title')
                URL=$(echo "$pr" | jq -r '.url')
                ASSIGNEES=$(echo "$pr" | jq -r -c '.assignees[].login')
                NOTIFICATIONS=""
    
                for ASSIGNEE in $ASSIGNEES; do
                  MEMBER_SLACK_ID=$(echo "$MEMBERS" | jq -r --arg MEMBER "$ASSIGNEE" '.[$MEMBER]')
                  MEMBER_SLACK_ID="<@${MEMBER_SLACK_ID}>"
                  if [ -z "$NOTIFICATIONS" ]; then
                    NOTIFICATIONS="$MEMBER_SLACK_ID"
                  else
                    NOTIFICATIONS="$NOTIFICATIONS $MEMBER_SLACK_ID"
                  fi
                done
    
                echo "------------------------------\\n" >> message
                echo "タイトル:$TITLE\\n" >> message
                echo "レビュワー:$NOTIFICATIONS\\n" >> message
                echo "URL:$URL\\n" >> message
              done
    
              {
                echo "MESSAGE<<EOF"
                cat message
                echo "EOF"
              }  >> "$GITHUB_OUTPUT"
    
          - name: Post a message in channel
            uses: slackapi/slack-github-action@v2.0.0
            with:
              webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
              webhook-type: incoming-webhook
              payload: |
                text: "${{ steps.create_message.outputs.MESSAGE }}"

    メッセージ作成のステップ- name: Create Messageの大まかな内容としては、

    • PRの情報と対応づけデータの取得
    • jqコマンドでPRの各項目情報を取得
    • メンバーのGitHubのアカウント名をSlackのメンバーIDに変換
    • メッセージを作成
    • メッセージを出力

    という流れになっています。

    メッセージに\\nと入れているのは、最後のステップ- name: Post a message in channelでSlackに作成されたメッセージを送る際に一行に変換されてしまうため、明示的に\\nとすることで- name: Post a message in channel内で改行されるようにしました。

    これを実行すると、一番初めに見せたような形で送られてくるかと思います。

    ちなみにメッセージの\\nなしだとこのように一行で送られてしまいます。

    スケジュール化しよう

    最後に、これまでは手動で実行していたものを毎日12時に通知するというようなスケジュールにしてみましょう。

    name: PR Notification to Slack
    
    on:
      schedule:
        - cron: '0 3 * * *'
    
    jobs:
      ...(略)

    GitHubActionsのスケジュールはあまり正確ではなかったり実行されなかったりするようです。試している中でも30分待っても実行されなかったり10分ほど遅れて実行されたりしました。

    docs.github.com

    12時過ぎに通知が送られてきてますね。

    最後に

    メッセージ作成の処理方法やそもそもの実装方針など、改良の余地はたくさんあるとは思いますが、1つのパターンとして主題を実装できたかなと思います。
    ベストな方法を比較できるように力をつけたいと思いました。