補習ほぼ確

学びや好きなことをただ自由に書く

Google Apps Script はじめました

GitHub Enterprise のリポジトリの、特定のラベルがついた issue にコメントがあった時に Slack に通知させたい」という目的で Google Apps Script をはじめたら思いの外楽しい。

上記を実現しようとしたとき GitHub integration for Slack であれば /github の Slash コマンドで十分ではある。

subscribeave-hikari/tips というリポジトリの issue のコメント(アクティビティ)を通知させる
またこの通知を bug のラベルがついた場合のみにフィルタする

スクリーンショット 2020-08-02 16 56 33

スクリーンショット 2020-08-02 16 56 42

コメント時以下のように通知される

スクリーンショット 2020-08-02 17 10 14

(通知解除時は /github unsubscribe
特定のリポジトリの全 issue に対してでは量が多すぎるので特定のラベルがついてるやつのみ通知させたい!というカスタムがしたいとき、 Google Apps Script でも GitHub API を使って、かつ通知を見た時テンションも上がるように Slack に通知させてみよ〜という感じに思い至った。

スクリプトは基本的な JavaScript + GAS 独自の関数、という感じなので新規のアプリ作成&導入手順やGCPでのログ参照周りを細かく残す ✍️

1. GAS で実行するWebアプリケーションを新規追加

GAS の「新しいアプリケーション」より新規のアプリを作成(初めて作成する場合は事前に Google ドライブなりから Google Apps Script のアプリを追加する必要あり)

スクリーンショット 2020-08-02 17 28 04

このようになっている

2. アプリケーションをデプロイしてGETリクエストしてみる

作成したアプリにHTTPリクエストを送るとGASでは

  • doGet
  • doPost

がトリガとなり実行される。
🔗 Simple Triggers

ということでwebアプリとしてデプロイして、GETの挙動を見てみる。
まず新規作成時 myFunction() になっているコードを以下に修正して、メニューバーの「関数を選択」を doGet を設定。

function doGet(e) {
  Logger.log(e)
}

プロジェクト名を識別しやすいものに変更して、公開 > ウェブアプリケーションとして導入 を選択すると以下のように表示される

bug_ラベルがついたコメントがpostされた場合にslackに通知

ここで execute the app as(実行ユーザ) を自身のアカウントに、Who has access to the app(アプリにアクセス可能なユーザ)を Anyone even anonymous (匿名ユーザを含む全員)に設定。
ここでの匿名ユーザとはGoogleアカウントを持っていない、アプリにアクセスする Slack のことと解釈している

そして Deploy を押すとデプロイしたアプリケーションのURLが表示されるので、

bug_ラベルがついたコメントがpostされた場合にslackに通知

このURLの末尾に ?hoge=fuga など適当なパラメータをつけてアクセスしてみるとこうなる。

スクリーンショット 2020-08-02 18 09 06

Logger でログを出力しているので、プロジェクトのダッシュボード>実行数を見るとログが出力されているのが確認できる。

スクリーンショット 2020-08-02 18 17 54

まずは作成したアプリケーションでGETリクエストをちゃんと受け付けられているという最小限の動作はOK 👌
Logger.log() をログ出力に使っているけど馴染み深い console.log() も同様に出力&ダッシュボードからの確認は可能。

今回は GitHub にコメントされたときの処理なので以降は doPost を使う。

3. Webhook の設定

リポジトリSettings > Webhooks > Add Webhook より以下のように設定。

  • Payload URL
    • 作成したアプリケーションのURL
  • Content type
  • トリガするイベントは Let me select individual events より、Issues Issue comments を選択

Add_webhook

4. POSTリクエストを受け付ける

doPost がトリガされるようアプリのコードを関数名だけ、以下のように修正。

function doPost(e) {
  Logger.log(e)
}

最初ハマったのだがスクリプトを書き換えたら保存だけでなく 公開 > ウェブアプリケーションとして導入 より都度デプロイし直さなければ反映されないので注意(当然)
その際 Project versionNew にしておく。バージョン更新時にコメント残せるので切り戻しも便利!
そしてデプロイの都度 Current web app URL とアプリケーションのURLを教えてくれるがこれは変更されないので、Webhookの設定更新は不要。

doPost に書き換えたので、GASがこのスクリプトを実行してPOSTリクエストを受け付けるようトリガの設定をする。

プロジェクトの ダッシュボード>トリガ より以下のようにトリガを追加

  • 実行する関数を選択
    • doPost
  • デプロイ時に実行
    • Head(最新バージョン)
  • イベントのソースを選択
    • 時間主導型
  • 時間ベースのトリガーのタイプ
    • 分ベース
  • 時間の間隔を選択(分)
    • 1分おき

スクリーンショット 2020-08-02 19 15 29

先ほどと同じくプロジェクトのダッシュボード>実行数を見るとログが出力されている。

スクリーンショット 2020-08-02 22 09 33

ここでトリガから実行される doPost の中身をデバッグしながら書いていくのだけど、デバッグ目的に Logger.log() が使いづらい(POSTリクエストの詳細が出力されない)ので console.log() を使用することにする。
console.log() での出力は Google Cloud Platform(GCP)の Stackdriver Logging を使用する。

5. GCP上でコンソールログを出力確認できるようにする

5-1. Google Apps Script用のプロジェクトをGCPで作成

Googleアカウントがあれば利用できる)GCPのTOPより、新しいプロジェクトを作成

スクリーンショット 2020-08-02 22 57 04

作成したプロジェクトを選択しダッシュボードを表示すると、プロジェクト情報に「プロジェクト番号」があるのでこれをコピー

ホーム_–_debug-gas_–_Google_Cloud_Platform

GASに戻って、リソース>Cloud Platform プロジェクト を選択。コピーしたプロジェクト番号をフォームに入力し設定ボタンを押すと、

bug_ラベルがついたコメントがpostされた場合にslackに通知

以下のように OAuth の設定を求められるので文中のリンクから設定画面に遷移。

bug_ラベルがついたコメントがpostされた場合にslackに通知

UserType で「外部」を選択

スクリーンショット 2020-08-02 23 10 10

次の画面でアプリケーション名だけ任意のものを入力し(GASのプロジェクト名と合わせた)保存。

スクリーンショット 2020-08-02 23 23 03

GASに戻って Cloud Platform プロジェクトの「プロジェクトの設定」を押すと、GCPのプロジェクトとGASの紐付けがされる。

スクリーンショット 2020-08-02 23 25 08

5-2. GCPのログビューアからコンソールログを参照

作成したGCPプロジェクトの メニュー>ロギング>ログビューア を開き、出力するログの種類をセレクトボックスより「Apps Script 関数」を選択しておく

GASの Logger.log(e) の1行を console.log() に変更し、GitHubから新規に issue を作成してみる

function doPost(e) {
  console.log('ログ出力:', JSON.parse(e.postData.getDataAsString()));
  return;
}

POSTリクエストの詳細をパースして出力しているため以下のようにGCPのログビューアより確認できる。

ログビューア_-_debug-gas_-_Google_Cloud_Platform

6. Slack の Webhook 設定

Slack のアプリ追加から Incoming Webhook を新規に作成し、Webhook URL をコピーする

Incoming_Webhook___Slack_App_ディレクトリ

ここで、GASのスクリプトプロパティを使って Slack Webhook URL を設定しておく ファイル>プロジェクトのプロパティ>スクリプトプロパティ にkey, value(URL)を保存

スクリーンショット 2020-08-02 23 59 32

bug_ラベルがついたコメントがpostされた場合にslackに通知

スクリプトプロパティに保存した key, valuePropertiesService で以下のように取得可能

PropertiesService.getScriptProperties().getProperty('slack_webhook_url');

7. GAS から Slack Webhook にリクエストを送る

デバッグしつつ以下のように POSTリクエストを受け取り、Slackの webhook にリクエストを送信する処理を書く。

const SLACK_WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty('slack_webhook_url');

function doPost(e) {
  if (!e.postData) {
    return;
  }
  var data = JSON.parse(e.postData.getDataAsString());
  // commentがない場合は通知しない
  if (!data.comment) {
    return;
  }
  callSlackWebhook(data.comment);
}

// Slackの webhook にリクエストを送る
function callSlackWebhook(comment) {
  var params = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({
      channel: '#general',
      link_names: 1,
      text: 'コメントされました:eyes:',
      attachments: [{
        color: '#ffd700',
        text: comment.body
      }],
    })
  };
  var response = UrlFetchApp.fetch(SLACK_WEBHOOK_URL, params);
  return response;
}

この内容でデプロイし GitHub の issue に テスト用issueにコメントしました とコメントすると以下のように通知される

スクリーンショット 2020-08-03 0 16 31

なお UrlFetchApp.fetch() で slack へのリクエストを記述してデプロイする初回は、アクセス許可を確認されるので表示に従う。

スクリーンショット 2020-08-03 0 07 39

8. 特定の issue に対してリクエストする

ここまでで issue にコメントがあった際Slackのチャンネルに通知する まではできたので発端の 「特定のラベルがついた issue にコメントがあった時に」対して行えるようにする

doPost で POSTリクエスト時のパラメータを e.postData.getDataAsString() で参照しており、

  • action
  • issue
  • user
  • labels
  • comment ...

といった内容が確認できるがPOST時にラベルの詳細(名前など)が確認できるのは「ラベルが issue に設定された時」( action: labeled )のみだったので、コメントされた時対象の issue のラベルが何か は issue に対して UrlFetchApp.fetch() でGETし確認することにした。

Slackの webhook にリクエストする callSlackWebhook() をコールする前後を以下のように書き換え、コメントされた issue が特定のラベルが付与されている場合にのみ発火させる

  var issue_info = UrlFetchApp.fetch(data.issue.url);
  var issue_parse = JSON.parse(issue_info.getContentText());
  issue_parse.labels.forEach(
    function(e) {
      if (e.name == 'bug') {
        callSlackWebhook(data.comment);
      }
      return;
    }
  )

また Authorization ヘッダを使用してアクセストークンでAPIにリクエストする場合は以下のように UrlFetchApp.fetch の引数にオブジェクトを渡す

  var github_token = PropertiesService.getScriptProperties().getProperty('github_token');
  var options = {
    'method': 'GET',
    'muteHttpExceptions': true,
    'Content-Type': 'application/json',
    'headers': {
      'Authorization': ' token '+ github_token,
    }
  };
  
  var issue_info = UrlFetchApp.fetch(data.issue.url, options);
  ...

issue のリンクもつけたりと payload の中身もカスタムして最終的には以下のようになった 👌

スクリーンショット 2020-08-03 1 02 14


Webエンジニアをしているものの、仕事でJS書くの月に10hもないのではくらいの感じなので GAS 書くの純粋に楽しいデバッグも(デバッグできるとこまでくると)楽しい。
サービス連携や業務の自動化にも可能性が無限なのでがしがし書くぞ〜

参考