目次
前提
以下の環境で実装、動作確認
要素 | バージョン |
---|---|
debian | 8.6 |
ruby | 2.2.2 |
sinatra | 1.4.7 |
概要
BacklogのWebhook機能を用いて、Sinatraで実装したWebアプリケーションを経由し、チャットワークにリアルタイム更新通知を行ったお話。
メール通知より圧倒的に早いリアルタイム通知による、プロジェクト管理の効率化ができる可能性を探る。
今回は試験的実装のため、課題作成時に課題の作成者、タイトル、URLをチャットワークのマイチャットに投稿するだけの簡単なシステムを実装した。
なお、今回は非常に軽量なシステムとなるので、Rubyの軽量WebフレームワークであるSinatraを採用しているが、Sinatraについては本記事から脱線するので詳しい解説は割愛する。
Webhookとは
端的に言えばアプリケーションで何らかの更新アクションが発生した時、その内容を特定のURLに対してPOSTしてくれる機能。
Backlogの場合、課題が追加された、更新された、削除されたなどの情報を、指定したURLに対してJSONでPOSTさせることができる。
技術とかツールでなく、ただの機能の名称なので注意。
Webhookの設定を行う
BacklogでのWebhookの設定はプロジェクト単位で行う。
プロジェクト設定に、Webhookの項目があるので、そこから設定する。
Webhook名と説明は適当に入力し、WebHookURLに更新通知をPOSTさせたいURLを入力する。当然、インターネットからアクセスできる場所でないといけないので、ローカル開発環境などを指定しても動作しない。
「通知するイベント」は、Backlogのどの更新アクションをWebhookで通知するかを指定できる。今回は課題の追加時にチャットワークで通知したいので、「課題の追加」にのみチェックを入れる
Webhookの受け口作成(Sinatra)
本記事では、Webhookの受け口に、Rubyの軽量WebアプリケーションフレームワークであるSinatraを用いる。といっても、POSTされたデータをチャットワークに流すだけなので、フレームワークも不要なレベルだが、導入が簡単なので今回はSinatraを用いることにした。
なお、Sinatra環境の構築には、手前味噌だが以下を用いた。
Sa2Knight/Sinatra-Skeleton: Sinatraアプリケーションを最短で構築する個人用リポジトリ
Backlogの更新内容を出力
Sinatraの詳細的な説明は割愛するが、以下がWebhookの受け口となるエンドポイントの実装である
require 'sinatra/base' require 'json' require 'pp' class App < Sinatra::Base post '/' do pp JSON.parse request.body.read return true end end
BacklogのWebhookでは、指定したURLに対して、リクエストボディがJSONのPOSTリクエストが飛んで来る。
上記コードでは、それを受け取ってJSONをparseし、標準出力している。
WebhookによってJSONがPOSTされ、以下のような標準出力が得られる
{"created"=>"2017-08-15T13:44:56Z", "project"=> {"archived"=>false, "projectKey"=>"DEV", "name"=>"個人開発", "id"=>38382, "subtaskingEnabled"=>false}, "id"=>19571304, "type"=>1, "content"=> {"summary"=>"課題タイトル", "key_id"=>275, "customFields"=>[], "dueDate"=>"2017-08-09", "description"=>"課題詳細", "priority"=>{"name"=>"中", "id"=>3}, "resolution"=>{"name"=>"", "id"=>nil}, "actualHours"=>nil, "issueType"=> {"color"=>"#7ea800", "name"=>"タスク", "displayOrder"=>0, "id"=>172585, "projectId"=>38382}, "milestone"=>[], "versions"=>[], "parentIssueId"=>nil, "estimatedHours"=>nil, "id"=>2889963, "assignee"=> {"name"=>"sa2knight", "id"=>85748, "roleType"=>255, "lang"=>"null", "userId"=>"sa2knight"}, "category"=>[{"name"=>"開発関係", "displayOrder"=>0, "id"=>79173}], "startDate"=>"", "status"=>{"name"=>"未対応", "id"=>1}}, "notifications"=>[], "createdUser"=> {"nulabAccount"=>nil, "name"=>"sa2knight", "mailAddress"=>nil, "id"=>85748, "roleType"=>1, "userId"=>nil}}
フォーマットはBacklog APIと概ね一緒なのでここでは割愛する。
BacklogAPIについては[Ruby] BacklogをCUIで操作してみる | QS-DEVSでも触れている。
通知に必要な情報のみ抜き出す
受け取ったデータのうち、今回必要なのは以下の3種類
- 課題キー
- 課題名
- 課題作成者
以下のようにコードを修正して、必要な情報のみ抜き出す。
post '/' do params = JSON.parse request.body.read @issue = { key: "#{params['project']['projectKey']}-#{params['content']['key_id']}", summary: params['content']['summary'], creator: params['createdUser']['name'], } pp @issue return true end
これで出力は以下のようになる
{:id=>"DEV-137", :summary=>"課題タイトル", :creator=>"sa2knight"}
チャットワーク連携の準備
次に、チャットワーク連携用のロジックを用意する。
こちらについても手前味噌だが、RubyでチャットワークAPIを利用するクラスを以前作っていたので、それをベースにする。
Sa2Knight/chatwork-ruby: RubyでチャットワークAPI呼んで遊ぶ
チャットワークAPIについては、以下参照
チャットワークAPIをRubyで利用する | QS-DEVS
今回はAPIの認証と、メッセージの送信ができればいいので、大幅にコードを削って以下のようなクラスに仕上げた。
require 'net/http' require 'uri' require 'json' require 'date' class Chatwork @@API_BASE = 'https://api.chatwork.com/v2' @@ROOM_ID = 'hogehogefugafuga' # tokenを指定してオブジェクトを生成 # tokenを省略した場合、環境変数を参照する def initialize(token = nil) @token = token || ENV['CHATWORKAPI'] end # ルームに新規メッセージを送信 # room_id: 対象のroomID # body: 投稿する本文 def sendMessage(body) url = '/rooms/' + @@ROOM_ID + '/messages' res = createHttpObject(url, :post, {:body => body}) return res.body ? JSON.parse(res.body) : [] end private # HTTPリクエストを送信する def createHttpObject(url, method, params = {}) api_uri = URI.parse(@@API_BASE + url) https = Net::HTTP.new(api_uri.host, api_uri.port) https.use_ssl = true api_uri.query = URI.encode_www_form(params) if method == :get req = createRequestObject(method, api_uri) req["X-ChatWorkToken"] = @token req.set_form_data(params) unless method == :get https.request(req) end # リクエストオブジェクトを生成する def createRequestObject(method, uri) case method when :get return Net::HTTP::Get.new(uri.request_uri) when :post return Net::HTTP::Post.new(uri.request_uri) when :put return Net::HTTP::Put.new(uri.request_uri) when :delete return Net::HTTP::Delete.new(uri.request_uri) end end end
環境変数”CHATWORKAPI”にAPIキーを設定し、このクラスのインスタンスを生成後、sendMessageメソッドを呼び出せばチャットワークにメッセージを送信できる。
なお、今回はメッセージの送信先として、マイチャットを指定している。
課題の更新を通知する
前項で実装したChatworkクラスを用いて、Webhookで送られた更新情報をチャットワークに通知する。
Sinatraの使い方としてはかなりお行儀が悪いが、プライベートメソッドmakeChatworkMessageを実装し、チャットワークに送信するメッセージを作成、それをチャットワークに送信するようにした。
require 'sinatra/base' require 'json' require 'pp' require_relative 'chatwork' class App < Sinatra::Base post '/' do params = JSON.parse request.body.read issue = { key: "#{params['project']['projectKey']}-#{params['content']['key_id']}", summary: params['content']['summary'], creator: params['createdUser']['name'], } Chatwork.new.sendMessage(makeChatworkMessage(issue)) return true end private def makeChatworkMessage(issue) message = "[info][title]#{issue[:creator]}さんが課題を作成しました[/title]" message += "[#{issue[:key]}] #{issue[:summary]}\n" message += "https://saknight.backlog.jp/view/#{issue[:key]}[/info]" end end
動作確認
サーバを起動し、課題の作成を行うと、チャットワークのマイチャットにリアルタイムで通知が届くことが確認できた。
所感
- 初めてWebhookを使ったが、かなりリアルタイムに近い。APIでpull型で取得するよりもコードもスマートに書けて良い
- メール通知より遥かに確実に、高速に通知できるので使いみちはあると思う
- BacklogWebhookの仕様上、インターネットで繋がる場所にサーバを建てる必要があるため、セキュリティにちょっと気をつける必要ありそう
- 以下のような工夫ができる余地がある
— プロジェクトごとに通知するチャットルームを切り替える
— 担当者に対応するユーザに対してToを付ける
— 課題のクローズ時にも作成者にToを付けて通知する