読者です 読者をやめる 読者になる 読者になる

俺の報告

RoomClipを運営するエンジニアの日報(多分)です。

Slack上で入力したメッセージを元にRedmineのチケットを自動発行させた話 - 日報 #156

今日は平山じゃないよ!
N氏の日報だよ!


この記事を読んで下さっている皆様の会社では、メッセやタスク管理の環境はどのような体制で組まれているでしょうか?

弊社では、メッセでのコミュニケーションをSlackで行い、タスクに関してはRedmineで回しています。 Slack自体はメッセとして非常に使いやすいのですが、使っていく中で問題もありまして、

例えば、
A君がB君に、とあるチャネルで仕事の依頼をし、
その直後にチャネル内でのやりとりが盛り上がって、200発くらいメッセージがPOSTされてしまったとしましょう。

結果、
A君がB君に依頼したという事を忘れたり見落としたりするという事が起こるかもしれませんし、わりとあるあるだと思います。

この問題は、A君とB君の間における管理体制が良くないというのもありますし、
そもそもフローする事が前提な場所で必ず実行しないといけないような依頼をするのも危なっかしい気もします。
そして、この手の問題が頻発するというのはお互いにとって辛い状態だと思います。

そういった問題を解決するには、

  • Slackでメッセをしている時に依頼内容が発生した時に、
  • 必ず忘れないようにタスク化される

という状態が望ましいのですが、これを自動で行う方法なんて無いんじゃないかと思ってましたが、 Slackのインテグレーション機能の「Outgoing Webhook」というのを使うと実現できそうという事がわかりました。

ですので今回は、Slack上で入力したメッセージを元にRedmineのチケットを自動発行させた話 について説明していきたいと思います。


事前準備
RedmineREST APIキーを取得しておく(省略)

手順

  1. Outgoing WebhookにAPIを登録
  2. RedmineにチケットをPOSTするAPIを作る
  3. 実際にtrigger_wordを打ち込んでみる

1. Outgoing WebhookにAPIを登録

f:id:tom_rc:20151112180429p:plain ・Trigger Word(s) Redmineへのチケット投稿であるかを識別するキーワードを入力する。 今回は、Slack上で、redmineと入力したら発火するようにしました。

・URL(s) ここには、Slackが叩くAPIのURLを入力します。 SlackからAPIが叩かれた時のメッセージの情報はPOST形式で送信されます。

2. RedmineにチケットをPOSTするAPIを作る

Outgoing WebhookからAPIが叩かれた時のPOSTデータは以下となりますので、

<?php
Array
(
    [token] => xxxxxxxxxxxxxx
    [team_id] => xxxxx
    [team_domain] => xxxxxx
    [service_id] => 123456
    [channel_id] => xxxxxxx
    [channel_name] => xxxxxxx
    [timestamp] => 1446199916.000004
    [user_id] => U0XXXXXX
    [user_name] => xxxxx.xxxxxx
    [text] => メッセージ本文だよー
    [trigger_word] => redmine
)

その情報を元に受けとなるAPIを作成していきます。 サンプルコードはこんな感じ

<?php
    //
    // Post From slack
    // (Slackから叩かれる)
    //
    public function actionSlack(){
        $postdata = $_POST;

        // SlackのユーザーIDとRedmineのユーザーIDを付き合わせる
        $user_mappings = array(
            'U02HRMCG8' => 1,    // user01 => [redmine user id]
        );

        // チケットのタイトルと本文
        $subject = $postdata['user_name']. 'さんからタスクが発行されました';
        $body = $postdata['text'];

        // 誰にチケットをアサインするか
        $assigned_to_id = 0;
        foreach($user_mappings as $slack_user_id => $user_id){
            if(strstr($body, $slack_user_id)){
                $assigned_to_id = $user_id;
                break;
            }
        }

        // チケットを所属させるプロジェクトのID
        $project_id = 23;

        // RedmineのREST APIにPOSTするデータ
        $data = array(
            'issue' => array(
                'project_id' => $project_id,
                'tracker_id' => 3,
                'priority_id' => 2,
                'assigned_to_id' => $assigned_to_id,
                'subject' => $subject,
                'description' => $body,
                'start_date' => date('Y-m-d'),
                'due_date' => date('Y-m-d')
            )
        );
        // チケット投稿用URL
        $planio_url = 'https://your.redmine.host/issues.json?key=';

        // REST APIのキー
        $post_url = $planio_url. 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

        // Redmineにチケットを登録する
        $result = $this->post_to_redmine($post_url, $data);

        // レスポンスデータからチケットIDを取り出す
        // (trigger_wordをレスポンスに入れてしまうとSlack上で無限ループするので注意)
        $result = array();
        $result_array = json_decode($result, TRUE);
        if(isset($result_array['issue'])){
            $issue_url = 'https://your.redmine.host/issues/'. $result_array['issue']['id']; 
            $postdata['text'] = "以下のチケットが発行されました\n". $issue_url;
            $result = $postdata;
        }
        header('Content-type: application/json');
        echo json_encode($result);
    }
    //
    // Redmineにチケットを投稿する
    //
    private function post_to_redmine($url, $data){
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $query = http_build_query($data);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
        
        $response = curl_exec($ch);
        
        if(!$response) {
            return false;
        }else{
            return $response;
        }
    }

3. 実際にtrigger_wordを打ち込んでみる

f:id:tom_rc:20151112180415p:plain

少し遅れてoutgoing-webhookからお返事が届きます。

この仕組み、別にチケット登録に限った事じゃなくて、もっと色々な事に活用できそう。