LoginSignup
8
11

More than 5 years have passed since last update.

backlog webhookを使ってpull-requestをslackに通知してみる

Last updated at Posted at 2016-04-15

はじめに

  • ゆるふわです

あわせて読みたい

ゴール

  • backlogのwebhookのイベント(pull-request作成、更新、コメント)をサーバー上のgolangで受け付けてslackのincoming webhookにリクエストする

公式ドキュメントでjson定義探す

Webhookでは、指定されたURLにデータをJSON形式でPUSHで送信します。JSONデータの属性は一部の共通部分を除き、イベントによって異なるためテスト実行等で確認して下さい。

><

自鯖に流して確認

pull-request作成

{
    "content": {
        "changes": [], 
        "comment": null, 
        "description": "XXX", 
        "diff": null, 
        "id": 111, 
        "issue": {
            "description": "XXX", 
            "id": 111, 
            "key_id": 111, 
            "summary": "XXX"
        }, 
        "number": 1, 
        "repository": {
            "description": "", 
            "id": 111, 
            "name": "test"
        }, 
        "summary": "XXX"
    }, 
    "created": "2016-04-14T14:31:33Z", 
    "createdUser": {
        "id": 111, 
        "mailAddress": null, 
        "name": "XXX", 
        "nulabAccount": null, 
        "roleType": 1, 
        "userId": null
    }, 
    "id": 111, 
    "notifications": [
        {
            "alreadyRead": false, 
            "id": 111, 
            "reason": 111, 
            "resourceAlreadyRead": false, 
            "user": {
                "id": 111, 
                "mailAddress": null, 
                "name": "XXX", 
                "nulabAccount": null, 
                "roleType": 111, 
                "userId": null
            }
        }, 
        {
            "alreadyRead": false, 
            "id": 111, 
            "reason": 111, 
            "resourceAlreadyRead": false, 
            "user": {
                "id": 111, 
                "mailAddress": null, 
                "name": "XXX", 
                "nulabAccount": {
                    "name": "XXX", 
                    "nulabId": "XXX", 
                    "uniqueId": "XXX"
                }, 
                "roleType": 111, 
                "userId": null
            }
        }
    ], 
    "project": {
        "archived": false, 
        "chartEnabled": true, 
        "id": 111, 
        "name": "XXX", 
        "projectKey": "XXX", 
        "subtaskingEnabled": true
    }, 
    "type": 111
}

pull-request更新

{
    "content": {
        "changes": [
            {
                "field": "description", 
                "new_value": "XXX", 
                "old_value": "XXX"
            }
        ], 
        "comment": {
            "content": "", 
            "id": 111
        }, 
        "description": "XXX", 
        "diff": "", 
        "id": 111, 
        "issue": {
            "description": "XXX", 
            "id": 111, 
            "key_id": 111, 
            "summary": "XXX"
        }, 
        "number": 111, 
        "repository": {
            "description": "", 
            "id": 111, 
            "name": "XXX"
        }, 
        "summary": "XXX"
    }, 
    "created": "2016-04-14T14:32:30Z", 
    "createdUser": {
        "id": 111, 
        "mailAddress": null, 
        "name": "XXX", 
        "nulabAccount": null, 
        "roleType": 111, 
        "userId": null
    }, 
    "id": 111, 
    "notifications": [], 
    "project": {
        "archived": false, 
        "chartEnabled": true, 
        "id": 111, 
        "name": "XXX", 
        "projectKey": "XXX", 
        "subtaskingEnabled": true
    }, 
    "type": 111
}

pull-requestコメント

{
    "content": {
        "changes": [], 
        "comment": {
            "content": "XXX", 
            "id": 111
        }, 
        "description": "XXX", 
        "diff": null, 
        "id": 111, 
        "issue": {
            "description": "XXX", 
            "id": 111, 
            "key_id": 111, 
            "summary": "XXX"
        }, 
        "number": 111, 
        "repository": {
            "description": "", 
            "id": 111, 
            "name": "XXX"
        }, 
        "summary": "XXX"
    }, 
    "created": "2016-04-14T14:32:04Z", 
    "createdUser": {
        "id": 111, 
        "mailAddress": null, 
        "name": "XXX", 
        "nulabAccount": null, 
        "roleType": 111, 
        "userId": null
    }, 
    "id": 111, 
    "notifications": [
        {
            "alreadyRead": false, 
            "id": 111, 
            "reason": 111, 
            "resourceAlreadyRead": false, 
            "user": {
                "id": 111, 
                "mailAddress": null, 
                "name": "XXX", 
                "nulabAccount": null, 
                "roleType": 111, 
                "userId": null
            }
        }, 
        {
            "alreadyRead": false, 
            "id": 111, 
            "reason": 111, 
            "resourceAlreadyRead": false, 
            "user": {
                "id": 111, 
                "mailAddress": null, 
                "name": "XXX", 
                "nulabAccount": {
                    "name": "XXX", 
                    "nulabId": "XXX", 
                    "uniqueId": "XXX"
                }, 
                "roleType": 111, 
                "userId": null
            }
        }
    ], 
    "project": {
        "archived": false, 
        "chartEnabled": true, 
        "id": 111, 
        "name": "XXX", 
        "projectKey": "XXX", 
        "subtaskingEnabled": true
    }, 
    "type": 111
}

上記3つをmergeしたstruct

type Backlog struct {
    Content struct {
        Changes []interface{} `json:"changes"`
        Comment struct {
            Content string `json:"content"`
            ID      int    `json:"id"`
        } `json:"comment"`
        Description string      `json:"description"`
        Diff        interface{} `json:"diff"`
        ID          int         `json:"id"`
        Issue       struct {
            Description string `json:"description"`
            ID          int    `json:"id"`
            KeyID       int    `json:"key_id"`
            Summary     string `json:"summary"`
        } `json:"issue"`
        Number     int `json:"number"`
        Repository struct {
            Description string `json:"description"`
            ID          int    `json:"id"`
            Name        string `json:"name"`
        } `json:"repository"`
        Summary string `json:"summary"`
    } `json:"content"`
    Created     string `json:"created"`
    CreatedUser struct {
        ID           int         `json:"id"`
        MailAddress  interface{} `json:"mailAddress"`
        Name         string      `json:"name"`
        NulabAccount interface{} `json:"nulabAccount"`
        RoleType     int         `json:"roleType"`
        UserID       interface{} `json:"userId"`
    } `json:"createdUser"`
    ID            int `json:"id"`
    Notifications []struct {
        AlreadyRead         bool `json:"alreadyRead"`
        ID                  int  `json:"id"`
        Reason              int  `json:"reason"`
        ResourceAlreadyRead bool `json:"resourceAlreadyRead"`
        User                struct {
            ID           int         `json:"id"`
            MailAddress  interface{} `json:"mailAddress"`
            Name         string      `json:"name"`
            NulabAccount interface{} `json:"nulabAccount"`
            RoleType     int         `json:"roleType"`
            UserID       interface{} `json:"userId"`
        } `json:"user"`
    } `json:"notifications"`
    Project struct {
        Archived          bool   `json:"archived"`
        ChartEnabled      bool   `json:"chartEnabled"`
        ID                int    `json:"id"`
        Name              string `json:"name"`
        ProjectKey        string `json:"projectKey"`
        SubtaskingEnabled bool   `json:"subtaskingEnabled"`
    } `json:"project"`
    Type int `json:"type"`
}

実装

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
)

type Slack struct {
    Channel   string `json:"channel"`
    IconEmoji string `json:"icon_emoji"`
    IconUrl   string `json:"icon_url"`
    Text      string `json:"text"`
    Username  string `json:"username"`
}

type Backlog struct {
    Content struct {
        Changes []interface{} `json:"changes"`
        Comment struct {
            Content string `json:"content"`
            ID      int    `json:"id"`
        } `json:"comment"`
        Description string      `json:"description"`
        Diff        interface{} `json:"diff"`
        ID          int         `json:"id"`
        Issue       struct {
            Description string `json:"description"`
            ID          int    `json:"id"`
            KeyID       int    `json:"key_id"`
            Summary     string `json:"summary"`
        } `json:"issue"`
        Number     int `json:"number"`
        Repository struct {
            Description string `json:"description"`
            ID          int    `json:"id"`
            Name        string `json:"name"`
        } `json:"repository"`
        Summary string `json:"summary"`
    } `json:"content"`
    Created     string `json:"created"`
    CreatedUser struct {
        ID           int         `json:"id"`
        MailAddress  interface{} `json:"mailAddress"`
        Name         string      `json:"name"`
        NulabAccount interface{} `json:"nulabAccount"`
        RoleType     int         `json:"roleType"`
        UserID       interface{} `json:"userId"`
    } `json:"createdUser"`
    ID            int `json:"id"`
    Notifications []struct {
        AlreadyRead         bool `json:"alreadyRead"`
        ID                  int  `json:"id"`
        Reason              int  `json:"reason"`
        ResourceAlreadyRead bool `json:"resourceAlreadyRead"`
        User                struct {
            ID           int         `json:"id"`
            MailAddress  interface{} `json:"mailAddress"`
            Name         string      `json:"name"`
            NulabAccount interface{} `json:"nulabAccount"`
            RoleType     int         `json:"roleType"`
            UserID       interface{} `json:"userId"`
        } `json:"user"`
    } `json:"notifications"`
    Project struct {
        Archived          bool   `json:"archived"`
        ChartEnabled      bool   `json:"chartEnabled"`
        ID                int    `json:"id"`
        Name              string `json:"name"`
        ProjectKey        string `json:"projectKey"`
        SubtaskingEnabled bool   `json:"subtaskingEnabled"`
    } `json:"project"`
    Type int `json:"type"`
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        var b Backlog
        err := json.NewDecoder(r.Body).Decode(&b)
        if !(err != nil && err == io.EOF) {

            s := Slack{Text: fmt.Sprintf("https://{YOUR_BACKLOG_DOMAIN}.backlog.jp/git/%s/%s/pullRequests/%d", b.Project.ProjectKey, b.Content.Repository.Name, b.Content.Number)}
            j, _ := json.Marshal(s)
            http.PostForm("https://hooks.slack.com/services/XXXXX/XXXXX/XXXXXXX", url.Values{"payload": {string(j)}})
        }
    })
    http.ListenAndServe(":80", nil)
}

URL送ったところで力尽きたorz

感想戦

  • json調べるのにほぼほぼの時間を使った
  • プロジェクト配下のすべてのリポジトリのpull-requestがwebhookで送られてくるので不要なものを無視するのがひと手間
  • コメントごとにslackに通知が来るのはだいぶノイズなのでn分間隔でpoolして重複は除去して通知とかやりたい(やるとはいってない
8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11