Ruby
Sinatra
Backlog
Chatwork
Webhook

Backlog + Chatwork + Sinatraで課題の更新通知を行う

More than 1 year has passed since last update.


概要

BacklogのWebhook機能を用いて、Sinatraで実装したWebアプリケーションを経由し、チャットワークにリアルタイム更新通知を行ったお話。

メール通知より圧倒的に早いリアルタイム通知による、プロジェクト管理の効率化ができる可能性を探る。

今回は試験的実装のため、課題作成時に課題の作成者、タイトル、URLをチャットワークのマイチャットに投稿するだけの簡単なシステムを実装した。

なお、今回は非常に軽量なシステムとなるので、Rubyの軽量WebフレームワークであるSinatraを採用しているが、Sinatraについては本記事から脱線するので詳しい解説は割愛する。


Webhookとは

端的に言えばアプリケーションで何らかの更新アクションが発生した時、その内容を特定のURLに対してPOSTしてくれる機能。

Backlogの場合、課題が追加された、更新された、削除されたなどの情報を、指定したURLに対してJSONでPOSTさせることができる。

技術とかツールでなく、ただの機能の名称なので注意。

参考 Webhookとは? on @Qiita


Webhookの設定を行う

BacklogでのWebhookの設定はプロジェクト単位で行う。

プロジェクト設定に、Webhookの項目があるので、そこから設定する。

1.png

Webhook名と説明は適当に入力し、WebHookURLに更新通知をPOSTさせたいURLを入力する。当然、インターネットからアクセスできる場所でないといけないので、ローカル開発環境などを指定しても動作しない。

「通知するイベント」は、Backlogのどの更新アクションをWebhookで通知するかを指定できる。今回は課題の追加時にチャットワークで通知したいので、「課題の追加」にのみチェックを入れる


Webhookの受け口作成(Sinatra)

本記事では、Webhookの受け口に、Rubyの軽量WebアプリケーションフレームワークであるSinatraを用いる。といっても、POSTされたデータをチャットワークに流すだけなので、フレームワークも不要なレベルだが、導入が簡単なので今回は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し、標準出力している。

サーバを立ち上げてから、以下のような課題を作成すると

2.png

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と概ね一緒なのでここでは割愛する。


通知に必要な情報のみ抜き出す

受け取ったデータのうち、今回必要なのは以下の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


動作確認

サーバを起動し、課題の作成を行うと、チャットワークのマイチャットにリアルタイムで通知が届くことが確認できた。

3.gif