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

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

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

- 準備
- まずはGitHubActionsとSlackを連携しよう
- PRの情報を取得しよう
- メンバーのGitHubアカウントの情報とSlackアカウントの情報の対応づけデータを用意しよう
- 通知するメッセージを作ろう
- スケジュール化しよう
- 最後に
準備
今回Slackへ通知を行うにあたりSlackのWebhookURLを使用します。
Slackappを作成してWebhookURLを用意しましょう。
WebhookURLを用意したらGitHubのシークレットに保存しておきます。

まずはGitHubActionsとSlackを連携しよう
今回はSlackが提供しているGitHubActionを利用してSlackに通知を送ります。
このページの記載の中で、今回は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の情報を取得します。
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 }}
...(略)
再度実行。

またエラーになりますね。
これはトークンの権限が足りないようです。ワークフロー内での権限調整もできたりするようですが今回はコンソールからリポジトリに対しての権限を緩めて対応します。
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などのデータはシークレットに入れないことがドキュメントに推奨されていました。
ドキュメントを読むと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分ほど遅れて実行されたりしました。

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