ローカル開発環境でZenHubのWebhookを受け取る方法を試す

最近、チームのプロジェクト管理ツールをJIRAからZenHubに変更しました。ZenHubはスクラム開発と親和性が高く、導入後のチームメンバーからの評判も上々です。

ZenHubはそのまま利用しても便利なツールではありますが、WebhookやAPIなどの機能が提供されており、これらを使用することでそれぞれの現場に存在するワークフローに対応した仕組みを作ることができます。

<img width="776" alt="スクリーンショット_2018-08-17_10_44_48.png" src="https://qiita-image-store.s3.amazonaws.com/0/38478/35e13016-23d7-e528-3300-044a9b37d2aa.png">

私自身も将来的に上図のように、特定のレーンに移動したときにIssueの入力項目が足りていない場合にコメントを付ける仕組みを作りたいと考えています。

本記事ではその初歩として、ローカルでZenHubのWebhookを受け取って表示するところまでを行いたいと思います。

# 完成形の共有
Node.jsで簡単なWebサーバーを作成して、Webhookを受け取ります。今回はngrokというサービスを利用し、ローカルの開発環境にWebhookリクエストが送信されるようにしています。

![webhook_ngrok.png](https://qiita-image-store.s3.amazonaws.com/0/38478/54a18afb-18d0-e5bb-898e-a0b6cdbab289.png)


実際にWebhookの受け取りに成功した場合は下記のようになります。

```
$ node index.js
Listening on 6000
{ type: 'issue_transfer',
  github_url: 'https://github.com/kentaro-m/auto-assign/issues/31',
  organization: 'kentaro-m',
  repo: 'auto-assign',
  user_name: 'kentaro-m',
  issue_number: '31',
  issue_title: 'Error: Resource not accessible by integration',
  to_pipeline_name: 'Review/QA',
  from_pipeline_name: 'In Progress' }
```

ZenHubのサポートページによると、受け取れるWebhookのイベントは以下の4種類となっています。

* `Issue transfer` レーンの移動
* `Estimate Set` 見積もりの設定
* `Estimate Cleared` 見積もりの消去
* `Issue Reprioritized` 優先度の変更 (レーン内での上下移動)

詳細は下記のサポートページをご覧ください。

[ZenHubIO/API: Learn how to use ZenHub's API.](https://github.com/ZenHubIO/API#webhooks)

# ZenHubの登録
GitHubアカウントで登録可能です。今回はパブリックリポジトリ向けに用意されている無料プランを使用します。

<img width="1279" alt="Screen Shot 2018-09-02 at 11.22.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/38478/ac3c760d-6278-d0d1-b839-bbf0e3768bcb.png">

もし、ZenHubの準備ができていない場合は、以下の手順で登録及びログインを行ってください。

[ZenHub](https://www.zenhub.com/)のWebサイトの右上の「Login」ボタンを押下します。その後に「Sign in with GitHub」を押下し、連携を許可すると、登録完了です。

適当なGitHubリポジトリを選択して、ボードを作成しましょう。 

# 新規プロジェクトの作成
ZenHubのボードの作成が完了したら、Webサーバーの準備を行います。

適用なディレクトリを作成し、`npm init`コマンドを実行して、Node.jsの新規プロジェクトを作成します。

```
$ mkdir zenhub-webhook-test
$ cd zenhub-webhook-test
$ npm init
```

# 関連パッケージのインストール
expressとbody-parserを使用するので、インストールします。

```
$ npm install --save express body-parser
```

# サンプルプログラムを貼り付ける
ZenHubのサポートページではWebhookを受け取るためのサンプルプログラムが公開されているので、今回はこちらを使用します。

`index.js`という名前のファイルをプロジェクトルートに作成して、そのファイルにプログラムを貼り付けます。

[ZenHubIO/API: Learn how to use ZenHub's API.](https://github.com/ZenHubIO/API#webhooks)

```js:index.js
var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var app = express();

http.createServer(app).listen('6000', function() {
  console.log('Listening on 6000');
});

app.use(bodyParser());

app.post('*', function(req, res) {
  console.dir(req.body);
});
```
# サンプルプログラムを実行する
下記コマンドを実行すると、Webサーバーが起動します。これで一旦Webサーバー側の準備は完了です。

```
$ node index.js
Listening on 6000
```
# ngrokコマンドのインストール
macOSかつHomebrewをお使いの場合は`brew cask`コマンドでインストール可能です。それ以外の場合はngrokの公式サイトでアカウント登録を行い、示された手順の通りにコマンドをインストールしてください。

c.f. [ngrokが便利すぎる](https://qiita.com/mininobu/items/b45dbc70faedf30f484e)

```
$ brew cask install ngrok 
```

# ngrokコマンドを実行する
先程作成したWebサーバーはデフォルトでは6000番ポートで接続を待ち受けをしています。

`ngrok http 6000`コマンドを実行することで、`localhost:6000`に外部からアクセスできるようになります。

`Forwarding`の項目のhttpsから始まるURLを次に使用するので、メモしておきましょう。

```
$ ngrok http 6000
ngrok by @inconshreveable                                       (Ctrl+C to quit)

Session Status                online
Session Expires               7 hours, 53 minutes
Version                       2.2.8
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://xxxxxxxx.ngrok.io -> localhost:6000
Forwarding                    https://xxxxxxxx.ngrok.io -> localhost:6000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              1       0       0.00    0.00    120.03  120.03

HTTP Requests
-------------
```
# ZenHubでWebhookの設定を行う
最後にZenHubのWebサイトでWebhookの設定を行います。作成したZenHubのボードを開いて、左下の自身のユーザー名を表示されているボタンを押下し、「Manage organizations」に選択します。

※下記リンクからも移動できます。

[ダッシュボード](https://app.zenhub.com/dashboard)

<img width="1276" alt="Screen Shot 2018-09-02 at 12.29.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/38478/91d610c3-f484-00e9-3836-44373989d441.png">

Slack & integrationsのページに移動します。Integrationsの項目を埋めていきます。

* `Select the service` Custom
* `Choose a Repository to connect` 連携させたGitHubリポジトリ
* `Integration description` Integrationに関する説明文 (オプション)
* `Webhook URL` ngrokのURL (先程メモした値)

入力が完了したら、「Add integration」を押下して、すべての設定は完了です。

<img width="1277" alt="Screen_Shot_2018-09-02_at_12_35_31.png" src="https://qiita-image-store.s3.amazonaws.com/0/38478/12492074-00f4-13ce-775f-f80965923cde.png">


# 実際にWebhookを受け取るテストをする
ボードに戻り、Issueのレーン移動をして、Webhookを受け取れるか確認してみます。

`Review/QA`から`Done`に移動させました。

<img width="1275" alt="Screen_Shot_2018-09-02_at_12_42_49.png" src="https://qiita-image-store.s3.amazonaws.com/0/38478/cf543643-3f8c-b2bc-e583-8eaa88efca1a.png">

下記の通りにIssueのレーン移動イベントを受け取ることができました。

```
$ node index.js
Listening on 6000
{ type: 'issue_transfer',
  github_url: 'https://github.com/kentaro-m/auto-assign/issues/31',
  organization: 'kentaro-m',
  repo: 'auto-assign',
  user_name: 'kentaro-m',
  issue_number: '31',
  issue_title: 'Error: Resource not accessible by integration',
  to_pipeline_name: 'Done',
  from_pipeline_name: 'Review/QA' }
```

実際には下記のように、特定のイベントをトリガーにGitHubやZenHubのAPIをコールして、各種リソースの状態変更を行うことが多いかと思います。

```js:index.js
var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var app = express();

http.createServer(app).listen('6000', function() {
  console.log('Listening on 6000');
});

app.use(bodyParser());

app.post('*', function(req, res) {
  console.dir(req.body);

  // 特定のレーンにIssueが移動したとき
  if (req.body.to_pipeline_name === 'Review/QA') {
    // GitHubまたはZenHubのAPIをコールして操作する
  }
});
```

# さいごに
ローカル開発環境でZenHubのWebhookを受け取る方法を紹介しました。いかがでしたか。

Webhookまわりのデバッグはリクエストを受けるエンドポイントを外部に公開しないといけないために大変な思いをしていました。ngrokを使用することで大分楽になったかと思います。

本記事がZenHubを便利に使うための仕組み作りのヒントになれば幸いです。