GithubのissueをSlackに通知する方法2(AzureFunctions+WebHook)

初めての投稿につき、やさしい心で見て頂けると嬉しいです。 
自分のメモ用を再編集したもので、不備が多いかと思いますが、誰かの役に立てば幸甚です。
 ~~細かい部分は覚えていない~~ 初期設定など情報が多々ある部分は、所々省略しています。

# やりたいこと
* Githubのissueを横断的にSlackで見たい
    * ID、タイトル、author、URL、reactionの数(投票などをするため)
* せっかくなので、RESTAPIではなくGraphQLを使ってみたい 

# 方法1でやったことと問題点
* 方法1では、heroku+botkitを使いやりたいことを実現しました。
     * [GithubのissueをGraphQLを使って集めSlackに通知する方法1(Heroku+Botkit)](https://qiita.com/kazutxt/items/565e06f77c6c6ea016b8)
* ただし、herokuの無料枠をこれだけで占有するのはもったいないので、AzureFunctionsに持っていきたいと考えて改良しました。
    * 複数のSlackにまたがらせることもできませんしね

# イメージ
方法1(前回の方式)
![shg.png](https://qiita-image-store.s3.amazonaws.com/0/259228/5c9901f5-9219-c8db-bd13-4a14b210850d.png)

方法2(今回の方式)
![sag.png](https://qiita-image-store.s3.amazonaws.com/0/259228/fed6bbab-355b-9236-bb9e-f8c86cc1456a.png)

# 動作環境
* MacOS 10.13.5
* git-graphql-client 1.0.0
* AzureFunctions 無料プラン 
* Slack 無料プラン
* Github 無料プラン

# STEP1 Azureにアカウントを作る
* Microsoft Azureはクラウドサービスの集合体です。
* 今回は、サーバーレスでファイルをあげるだけでURLを呼ぶとプログラムが実行されるAzureFuctionsを利用します。
* 類似するサービスに AmazonWebService Lambda があります。
* 今回は、無料枠 で行います。
* アカウントの作成や資材のアップロードは様々な先人の方がノウハウを残しているので省略します。
    * 手抜きでごめんなさい。公式ドキュメントを参考にするとよいと思います。
    * https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-azure-function
    
# STEP2 SlackからWebHookのURLを設定する
* 日本語化している場合は、slackをカスタマイズ ⇒　App管理　⇒ カスタムインテグレーション　⇒ 発信/着信Webフックからいけます
* 発信WebフックがAzureFunctionsを呼び出すもの
    * 引き金となる言葉とazureの関数のURLを設定します。
    * azure Functions側のソースコードが表示されている画面で、` 関数のURLの取得` で表示されるものを設定
* 着信WebフックがAzureFUnctionsから投稿されるもの
    * どこのチャンネルに投稿されるのかを指定します
    * WebhookURLは、azureFunction側の送信先のURLに指定します(詳しくは後述するソースを参照)

# STEP3 GithubのPersonalAccessTokenを払いだす
* 細かい手順は、こちらも様々な先人の方がノウハウを残しているので省略します。
    * 手抜きでごめんなさい。先人方の記事を参考にされるとよいと思います。
    * https://qiita.com/kz800/items/497ec70bff3e555dacd0
* 公開しているプロジェクトであれば、なにも権限がなくて大丈夫です

# STEP4 GraphQLのクエリを作成する
* 公式APIドキュメント：https://developer.github.com/v4/
* 動作を確認するエクスプローラ：https://developer.github.com/v4/explorer/

作成したリクエスト

* owner,name,labelsは自分の条件に合わせて使ってください
    * 今回は、ステータスがOpenで特定のラベルが付いたissueを集めました
    * 条件の追加をする場合は、()内を公式APIドキュメントをみてカスタマイズしてください

```js
{
  repository(owner: "xxx", name: "yyy") {
    issues(last: 100, states: OPEN, labels: "zzz") {
      edges {
        node {
          number
          title
          url
          author {
            login
          }
          reactions(last: 100) {
            nodes {
              content
            }
          }
        }
      }
    }
  }
}
```

応答イメージ

```json
{
  "data": {
    "repository": {
      "issues": {
        "edges": [
          {
            "node": {
              "number": 1,
              "title": "タイトル",
              "url": "https://github.com/XXX/YYY/issues/1",
              "author": {
                "login": "user"
              },
              "reactions": {
                "nodes": [
                  {
                    "content": "THUMBS_UP"
                  }
                ]
              }
            }
          }
        ]
      }
    }
  }
}
```

# STEP5 package.jsonを書く
* 最低限の名前とバージョン、依存関係のgithub-graphql-clientを記述したpackage.jsonをファイルのアップロードでサーバに置く
    * 下記のサンプルを参照
    * あるいは、プラットフォームの構成でコンソールからnpmでインストールしてもOKです
    
```json
{
  "name": "topic-bot",
  "version": "1.0.0" ,
  "dependencies": {
    "github-graphql-client": "1.0.0"
  }
}
```

* girhub-graphql-client
    * https://www.npmjs.com/package/github-graphql-client
    
# STEP6 プログラム本体を記述する
* 関数の＋ボタンで新規作成⇒HTTPtriggerで新しい関数を作る
    * 許可されているHTTPメソッド:All methods
    * モード：Standard
    * 要求パラメータ名：req
    * ルートテンプレート：ルートテンプレート
    * 承認レベル：Function
* アプリケーションの設定で、Githubのトークンを設定する

プログラム本体

```js
module.exports = function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    // 準備
    if (!process.env["GITHUB_TOKEN"]) {
       context.res = {
            status: 400,
            body: "please set API token environment value."
        };
        context.done();
    }
   
    var request = require('request');
    var myquery =
        `{
        repository(owner: "xxx", name: "yyy") { 
            issues(last: 100, states: OPEN, labels: "zzz") { 
                edges { 
                    node { 
                    number 
                        title 
                        url 
                        author { 
                            login 
                        } 
                        reactions(last: 100) { 
                            nodes {  
                                content 
                            } 
                        } 
                    }
                } 
            }
        }  
    }`;
    var githubToken = process.env["GITHUB_TOKEN"];
    
    var issues = [];
    var client = require('github-graphql-client');
    // githubに問い合わせ
    client({token: githubToken,query: myquery}, function (err, res) {
        if (err) {
            context.log("error");
            context.log(err);
        } else {
            // 成功した場合
            var resData = res.data.repository.issues.edges;
            for (i = 0; i < resData.length; i++) {
                var issue　 = {};
                issue.number = resData[i].node.number;
                issue.title = resData[i].node.title;
                issue.url = resData[i].node.url;
                issue.author = resData[i].node.author.login;
                issue.reaction = resData[i].node.reactions.nodes.length;
                issues.push(issue);
            }
            function compareReaction(a,b){
                return b.reaction - a.reaction;
            }
            issues = issues.sort(compareReaction);

            // チャットに返信
            var result = ""
            for (i = 0; i < issues.length; i++) {
                var resultLine = "#" + issues[i].number + ":" + issues[i].title + " :studio_microphone: " + issues[i].author + " :+1: " + issues[i].reaction + "pt " +
                "<" + issues[i].url + "|詳細>\n\n";
                result += resultLine;
            }

            var options = {
                url: {着信Webフックで表示されているURL} ,
                form: 'payload={"text": " '+ result +'"}',
                json :true
            };
            request.post(options, function(error, response, body){
            if (!error && response.statusCode == 200) {
                    context.log('ok');
                } else {
                context.log('error');
                }
            });
        }
    })
    context.done();
};
```
* reactionsはtotalCountを使ってもOK
    * 今回は、個別の合計や特定の要素は不要で、全体数がほしいのでこのようにしています。

## 上手くいかなかったこと
* botkitを使っても最初はうまくいくのですが、15-30分たつと接続が切れて、再接続のオプションを設定してもダメでした
* 内容にバグやセッションの維持？があると、正しく切り替えが行われないのか、多重に応答するようになりました。

# STEP7 AzureFunctionsにあげて動作確認
* Slackで実際に呼ぶ

![af.png](https://qiita-image-store.s3.amazonaws.com/0/259228/db420569-adb2-5607-6cb0-ae233ad38ed6.png)