Sinatra」タグアーカイブ

SinatraでFlash(1回きりのセッションデータ)を用いる

備忘録

前提

以下の環境で動作確認済み

$ ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
$ gem -v
2.4.5
$ bundle -v
Bundler version 1.13.4

FLASHとは

直後のHTTPリクエストのみで使用するセッションデータ。
POSTでデータを登録した後にGETにリダイレクトし、登録の結果メッセージを表示するのに使ったりする

rack-flash3をインストール

gem install rack-flash3

bundlerを使う場合
Gemfileに以下を追加し、bundle install

 gem "rack-flash3"

プログラム例

‘/’ 経由で’/hoge’にアクセスした場合に、’flash data’を出力。
直接’/hoge’にアクセスしたり、リダイレクト後に再読込した場合に’no data’を出力。

require 'sinatra/base'
require 'rack/flash'

class App < Sinatra::Base

  configure do
    use Rack::Flash
  end

  get '/' do
    flash[:hoge] = 'flash data'
    redirect '/hoge'
  end

  get '/hoge' do
    if flash[:hoge]
      flash[:hoge]
    else
      'no data'
    end
  end

end

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

前提

以下の環境で実装、動作確認

要素 バージョン
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とは? on @Qiita

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を付けて通知する