Fluct+Backlog webhook+Chatwork APIでバックログのイベントをチャットワークに通知してメール確認作業から解放される(少しだけ)

Fluct+Backlog webhook+Chatwork APIでバックログのイベントをチャットワークに通知してメール確認作業から解放される(少しだけ)

はじめに

ランチェスターではタスク管理ツールとしてバックログを使っていて非常に便利なのですが、新規課題やコメントなど更新があったときに確認する手段が目視だったりメールで通知だったりで結構大変な感があります。そこで普段使っているチャットワークに通知させてメール確認という苦行から(少し)解放されたいと思います。

バックログにはイベントに対してwebhookが用意してあり、URLを設定することでそのURLに対してイベントに関するパラメータをPOSTで送信します。ここにチャットワークのAPIのURLを設定してお手軽通知したいところですがヘッダー等を指定できないのでそのままではAPIを利用できません。
そこでAWSのAPI GatewayとLambdaを利用して通知を実装してみたいと思います。

イメージはこんな感じです。
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-11-28-19-12-19

実装

実装にはFluctというツールを使います。

FluctはAmazon API GatewayとAWS Lambdaを組み合わせて1つのWebアプリをつくるのを支援するためのツールです。AWS Lambdaを使う場合はNode.jsかJava 8の環境で動くコードを書くことになるので、これに合わせてFluctはNode.jsで実装されています。Webアプリの開発者は、開発に使う環境にFluctをインストールし、fluct というコマンドを利用しながら開発を進めていくことになります。

http://qiita.com/r7kamura/items/80e5e9b2f5c9d1b3497e

Fluctのインストール

Fluctはnpmでインストールします。動作にはnode.js 0.12以上が必要とのことなのでない場合は入れておきます。

$ brew install node
$ npm install fluct --global

インストール出来たらfluctコマンドで通知用アプリを作成します。apiのエンドポイントはchatwork_notificationsとします。

$ fluct new backlog_webhook

これで以下のファイルが作成されます。

Created ./backlog_webhook
Created ./backlog_webhook/README.md
Created ./backlog_webhook/.gitignore
Created ./backlog_webhook/actions
Created ./backlog_webhook/actions/.keep
Created ./backlog_webhook/package.json
$ cd backlog_webhook
$ fluct generate chatwork_notifications

以下のファイルが作成されます。

Created ./actions/chatwork_notifications
Created ./actions/chatwork_notifications/index.js
Created ./actions/chatwork_notifications/package.json

IAMロールの設定

Fluctのインストールが出来たら実装の前にAWS上でFluctで必要になるIAMロールの設定をします。
[IAM] -> [ロール] -> [新しいロールの作成]と進みFluct用のロールを作ります。
iam01
次にロールタイプの選択でAWS Lambdaを選択します。
iam02
次にポリシーのアタッチでAWSLambdaBasicExecutionRoleを選択します。
iam03
これでロールを作成します。

aws-cliのインストール・設定

githubのREADME等には特に書いてなかったのですが、どうやらFluctは内部でAWS CLIを使ってるようですので、無ければインストールする必要があります。その際接続するためのIAMユーザも一緒に作っておきます。
[IAM] -> [ユーザー] -> [ユーザーを追加]から新しくユーザーを作成します。
AWS CLIからアクセスするユーザなのでプログラムによるアクセスにチェックします。
iam04
ポリシーはAWSLambdaFullAccessとAmazonAPIGatewayAdministratorをアタッチします。
iam05

ユーザを作成したらそのユーザのアクセスキーIDとシークレットアクセスキーが書いてあるcsvをダウンロードしておきます。
このIDとキーをAWS CLIの認証に使います。

$ pip install awscli
$ aws configure
AWS Access Key ID [None]: 作成したユーザのAccessKeyID
AWS Secret Access Key [None]: 作成したユーザのSecretAccessKey
Default region name [None]: ap-northeast-1
Default output format [None]: json

Fluctの実装

AWS CLIの設定が完了したらいよいよFluctアプリ内にメインのコードを実装します。
まずbacklog_webhook/package.jsonを以下のように修正します。

{
  "name": "backlog_webhook",
  "private": true,
  "fluct": {
    "accountId": 作成したロールのARNにある数字,
    "restapiId": null,
    "roleName": "fluct-role",
    "region": "ap-northeast-1"
  },
  "dependencies": {
    "aws-sdk": "^2.2.3"
  }
}

backlog_webhook/chatwork_notifications/package.jsonを以下のように修正します。

{
  "name": "chatwork_notifications",
  "private": true,
  "fluct": {
    "contentType": "application/json",
    "httpMethod": "POST",
    "path": "/chatwork_notifications",
    "statusCode": 200
  }
}

次にメインとなるjsコードを実装します。
backlog_webhook/actions/chatwork_notifications/index.js

var https = require('https');
var querystring = require('querystring');
// backlogユーザID: チャットワークユーザIDのオブジェクト
var bl_cw_ids = {
  123456789: '[To:12345] ほげ山さん',
  234567890: '[To:67890] ふが森さん'
}
// backlogプロジェクトID: チャットワークroom idのオブジェクト
var project_room_ids = {
  1234567890: '1234567',
};
// チャットワークAPI key
var CHATWORK_API_KEY = チャットワークAPIkey;
var DEFAULT_ROOM_ID = チャットルームIDが指定されていない場合のデフォルトルームID;
var ROOM_ID;
var BACKLOG_URL = 'https://xxx.backlog.jp/';

function set_chatwork_room_id(event) {
  ROOM_ID = project_room_ids[event.project.id];
}

function create_link(event, content) {
  var url = BACKLOG_URL + "view/" + event.project.projectKey + "-" + content.key_id;
  if (content.comment && content.comment.id) {
    url += "#comment-" + content.comment.id;
  }
  return url;
}

function create_links(event, link) {
  var urls = [];
  link.forEach(function(content) {
    urls.push(create_link(event, content));
  });
  return urls;
}

function create_message(event) {
    var notice_labels = [];

    if(event.notifications.length > 0) {
        event.notifications.forEach(function(ntf){
            notice_labels.push(bl_cw_ids[ntf.user.id]);
        });
    }
    notice_labels = notice_labels.filter(function (x, i, self) {
        return self.indexOf(x) === i;
    });

    var message = '';
    notice_labels.forEach(function(val){
      message += val + "\n";
    });

  switch(event.type) {
    case 1:
        return message + "[info][title]" + event.createdUser.name + "が「" + event.content.summary + "」を追加しました。[/title]" + create_link(event, event.content) + "[/info]";
    case 2:
    case 3:
        return message + "[info][title]" + event.createdUser.name + "が「" + event.content.summary + "」を更新しました。[/title]" + create_link(event, event.content) + "\n" + event.content.comment.content + "[/info]";
    case 4:
        return message + "[info][title]" + event.createdUser.name + "が課題を削除しました。[/title]" + create_link(event, event.content) + "[/info]";
    case 6:
        return message + "[info][title]" + event.createdUser.name + "がWikiを更新しました。[/title]" + create_link(event, event.content) + "[/info]";
    case 14:
      return message + "[info][title]" + event.createdUser.name + "が課題をまとめて更新しました。[/title]" + create_links(event, event.content.link).join("\n") + "[/info]";
  }
  return "unknown event type: " + event.type;
}

function post_to_chatwork(message, context) {
    var postData = querystring.stringify({ body: message });

    var options = {
        host: 'api.chatwork.com',
        port: 443,
        method: 'POST',
        path: '/v1/rooms/' + ROOM_ID + '/messages',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': postData.length,
            'X-ChatWorkToken': CHATWORK_API_KEY
        }
    };

    var req = https.request(options, function (res) {
        res.on('data', function (d) {
            process.stdout.write(d);
        });
        res.on('end', function () {
            context.done();
        });
    });
    req.on('error', function (err) {
        console.log(err);
    });
    req.write(postData);
    req.end();
}

exports.handler = function(event, context) {
    console.log('start event');
    console.info('event', event);
    set_chatwork_room_id(event.requestParameters);
    console.info('context', context);
    var message = create_message(event.requestParameters);
    console.log('send message: ' + message);
    // ROOM_IDが設定されていない場合は警告メッセージを表示
    if(!ROOM_ID) {
      ROOM_ID = DEFAULT_ROOM_ID;
      message = "チャットワークルームIDが設定されていません。\nバックログのプロジェクトID「" + event.project.id + "」とチャットワークのルームIDを対応付けてください。";
    }
    post_to_chatwork(message, context);
};

メインのコードは参考にしたページのコードそのままなのもアレなので通知ユーザがいる場合はチャットワークでもTOをつけるようにしてみました。
また複数プロジェクトと複数チャットルームにも対応してみました。(プロジェクトやルームやメンバーのID等は事前にCloudWatchのログなどから調べておく必要はありますが…)

デプロイ

実装が終わったらAPI GatewayとLambdaにデプロイします。

$ fluct deploy

これだけでデプロイすることが出来ます。
正常にデプロイ出来るとLambdaに関数が登録され、以下のようにAPI Gatewayにエンドポイントが作成されます。
apigateway01
このURLの末尾に/chatwork_notificationsをつけたURLがwebhook用のURLになります。これをバックログに設定します。

バックログwebhook設定

webhookを設定したいプロジェクトに移動して、[プロジェクト設定] -> [webhook] -> [webhookを追加する]を選択します。
webhook名、説明は適当に入力し、Webhook URLには先程のURLを入力、通知するイベントはすべてを選択しWebhookを追加します。

テスト

これでバックログを更新するとこんな感じのチャットが投稿されます。
chat
便利ですね

おわりに

というわけでチャットワークにバックログのイベントを通知してみました。
最初はバックログのパラメータから名前を使ってチャットワークのユーザやルームをAPI経由で検索して取得できないかと頑張ってみましたが、名前の表記ゆれやそもそも結婚して名字が変わってる人などいてしんどかったので結局紐付けをコード内に埋め込みました。
ともあれこれでメール確認作業から(ほんの少し)解放されました。めでたし!

参考にしたページ

http://qiita.com/r7kamura/items/80e5e9b2f5c9d1b3497e
http://qiita.com/noboru_i/items/7255fe225854a4876b0d
http://qiita.com/zaru/items/bab992b9438f07740eb9

TAG

  • このエントリーをはてなブックマークに追加
kurashita
エンジニア kurashita kurashita

基本的にRuby on Railsで開発してます。最近はvue.jsも。好きな塔は円城です。