Slack Events API + GASで簡易的にGitHubのアクティビティを見てみる
この記事は Ubiregi Advent Calendar 2020 21日目のエントリです。
本日は、GitHub上のアクティビティを監視・集計する方法としてこんなやり方もできる、という小ネタをご紹介。
Pull Request(以下、PR)やIssueの状況をウォッチするために、GitHubのSlack Appを連携させて使っている人も多いだろう。
対象のリポジトリや項目をスラッシュコマンドで設定すれば、それぞれのアクティビティがSlack上に流れてくるというもの。
これはつまり、GitHub上のアクティビティをSlackのチャンネル投稿イベントとしてSlack Events APIで扱うことができるということでもある。
GitHub連携をしているSlackチャンネルの投稿を監視するだけで、マージ待ちのPRをリスト化したり、PRのopenからcloseまでの時間を計測したりといったことが簡易的ながら可能になる。
そんなことしなくても普通にGitHub webhooks使えばいいじゃん、と言われればまったくその通り。
そこが小ネタたる所以である。
とはいえ、Slack Events APIをすでに使い慣れている場合はもちろんのこと、
といった特徴があるので、こちらの方が使いやすい場面ももしかしたらあるかもしれない。
ここでは、GASでSlack上のGitHub通知の内容を取得できるようにするまでの流れと、取得した情報のどこに何が入っているかまでを紹介する。
GASプロジェクトの準備
Slack Appの実装にここではGoogle Apps Script(以下、GAS)を使用する。
GASを選択するメリットとして次のようなものが挙げられる。
- Googleアカウントがあればすぐに使い始められる
- 実行回数が無制限
- 1回あたりの実行時間には6分までという制限があるが、Slackの投稿イベントで飛んでくるjsonはとても小さいので、よほど複雑で重たい集計をしようとしない限りは収まるはず
- スプレッドシートなど他のGoogleサービスとの連携が容易
- 集計結果をそのまま共有可能な形にしたり、スプレッドシートの数式も組み合わせて集計自体を楽にすることもできる
※V8ランタイム登場前に書いた少し古いコードであること、元々jsが専門外であることなどから、洗練されたコードにはなっていない点は何卒ご容赦いただきたい。
プロジェクトの作成
GASのホームから「新しいプロジェクト」を押して新規のGASプロジェクトを作成する。
スプレッドシートと組み合わせて使うことが決まっていれば、まずスプレッドシートを作成して ツール>スクリプトエディタ
と進んでシートに紐づいたプロジェクトとして作成してもいい。
URL確認イベントへの対応
このプロジェクトでSlackからのイベント通知を扱っていくわけだが、イベント通知の送信先URLを設定すると同時にURLの確認イベントが飛んでくる。
中身を実装する前に、まずその対応を先に入れておくと設定がスムーズである。
GASでは doPost()
という名前の関数を実装しておくと、WebアプリとしてデプロイしたときのPOSTリクエストの受け口になる。
こんな感じで、url_verification
イベントが飛んできたらその中に含まれるchallenge
というフィールドの値をそのまま返すようにする。
function doPost(e) { if (e.postData == "FileUpload") { var contents = JSON.parse(e.postData.contents); var type = contents.type; //URL確認 if (type == "url_verification") { return ContentService.createTextOutput(JSON.stringify(contents.challenge)).setMimeType(ContentService.MimeType.TEXT); } } }
ここまででいったん、右上の デプロイ>新しいデプロイ
から、現在の状態をWebアプリとしてデプロイする。
ここで「アクセスできるユーザー」は「全員」になっている必要がある。
一度デプロイすると、 デプロイ>デプロイを管理
からURLが取得できるようになる。
Slack Appの準備
Slack Events APIのイベントを受け取るにはSlack Appを作成することになる。
ここでは今回やること向けに特化したポイントに絞って説明していくので、Slack App自体のセットアップ方法などについては別途ドキュメントを参照されたい。
Enabling interactions with bots | Slack
スコープの設定
作成したSlack AppのOAuth & Permissionsのメニューからスコープを設定する。
投稿イベントの取得自体は、Bot Tokenのチャンネル投稿の読み取り権限 channels:history
さえあれば動作する。
集計結果を投稿させたい場合などは書き込み権限 chat:write
も必要になる。
User Tokenはここでは使用しない。
イベントへの登録
Event Subscriptionsのメニューから Enable Events
のスイッチをOnにし、受け取るイベントを設定する。
前述の通りチャンネル投稿を見たいだけなので、Bot User Eventsの message.channels
を選べばOK。
channels:history
スコープが必要なことはここで言われるので、こちらを先に設定しても良い。
イベントの通知先URLの設定
同じくEvent Subscriptionsのメニューで、イベントの通知先URLを設定する。
Request URL
の欄に先ほど作ったGASプロジェクトのURLを貼り付ける。
先ほど作っておいたURL確認の実装が正しければ、すぐに確認が完了し Verified
というラベルと緑色のチェックマークがつく。
これで、Slackからチャンネル投稿を受け取る準備ができた。
GitHub通知の投稿内容の取得
message.channels
イベントで飛んでくるjsonの中身はこのようになっている。
ここから event
要素を取り出して見れば良いのだが、全てのチャンネルに投稿があるたびにこの関数が呼び出されることになるので、まず扱うケースを適切に絞り込んでいく必要がある。
チャンネルIDが監視したいチャンネルでない・発言者がGitHub Appではない投稿は全て除外してしまおう。
function doPost(e) { if (e.postData == "FileUpload") { var contents = JSON.parse(e.postData.contents); var type = contents.type; //URL確認 if (type == "url_verification") { return ContentService.createTextOutput(JSON.stringify(contents.challenge)).setMimeType(ContentService.MimeType.TEXT); } else if (type == "event_callback") { var event = contents.event; var event_type = event.type; if (event_type == "message") { var user = event.user; //投稿したユーザーID var channel = event.channel; //投稿があったチャンネルID if(channel == <監視したいチャンネルID> && user == <GitHub AppのユーザーID>){ //投稿内容を取得 var attachment = event.attachments[0]; } //それ以外は何もしない return null; } } } }
GitHub Appの投稿から情報を取得する際にわかりにくいのが、内容が全て attachments
扱いであるという点。
attachments
は配列として返されるので、内容にアクセスする前にまず配列として取得してから1つ目の要素を取り出す必要がある。
Pull request opened by hogehoge
のメッセージなどいかにも本文、 text
であるように見えるのに、実際は attachments
の中の pretext
要素である。
attachments
要素のフィールド一覧は公式ドキュメントの以下のセクションで見ることができる。
Creating rich message layouts | Slack
GitHubの通知の項目はそれぞれ以下のフィールドに格納されている。
項目 | attachments内のフィールド名 |
---|---|
本文(Pull request opened by hogehoge など) |
pretext |
PRやIssueのauthor名 | author_name |
PRやIssueのタイトル | title |
PRやIssueへのリンク | title_link |
PRやIssueの本文 | text |
リポジトリ名 | footer |
イベントが作成された日時のタイムスタンプ | ts |
さらに、タイトルについては #id タイトル
の形式にまとめられているので、少し乱暴だが
var title = attachment.title; var id = title.match(/[0-9\s+]/g).join(''); id = id.split(' ')[0];
こんなことをやると、PRやIssueのIDだけを取り出すことも可能になる。
ステータスについては pretext
に opened
、merged
、closed
などの単語が含まれているかどうか、で簡易的に判別が可能である。
全てのイベントは日時も一緒にわかっているので(タイムスタンプからの変換は必要だが)、これらの項目を使った集計となるとけっこういろんなことができる。
集計以外にも、例えばPRがマージされたらデプロイ手順を投稿する、といった使い方もできる。
簡易的に、を繰り返しているのはこれらの仕様は変わる可能性があるためである。
Slack向けGitHub Appのソースコードは以下で公開されているので、「なんか挙動変わったかな」と思ったら覗いてみると良いかもしれない。
(この記事は過去の実装経験に加え現時点でのGitHub Appのコードもざっと確認した上で書いている。が、もし見落としがあればご容赦願いたい)
https://github.com/integrations/slack
応用例についてはまた機会があれば触れる……かもしれない。