HubotにGitHubのプルリクエストを自動でアサインさせる

  • 86
    Like
  • 0
    Comment
More than 1 year has passed since last update.

概要 / 目的

いつも決まった人がプルリクエストに対するコードレビューを行っていると、以下のようなデメリットがあります。

  • コードレビューの属人化
  • コードレビューする側とされる側がはっきり別れてしまい、フラットな関係でのチーム開発を阻害する
  • コードレビューが特定の人に集中してしまい、他の重要な仕事が進みにくくなる

そこで、コードレビューをチーム内のメンバーに割り振るのですが、この割り振りを人力でやるのは非生産的なので、Hubotを使って自動化しました。

ここでの説明で前提となっているChatOpsな環境の構築方法や運用方法は以下を参考にしてください。

Hubotによる自動アサインの概要図

Untitled(1).png

設定 / 実装

GitHubチームの作成

GitHubのWEBコンソールからプルリクをアサインする用のチームを作成します。
このチームの中から自動で1人がアサインされる仕組みとなります。

y_スクリーンショット_2014-09-20_15_45_26.png

今回は以下の2つのチームを用意しました。

  • 商用ブランチへのプルリクを担当するチーム(リリース担当チーム)
  • それ以外のプルリクを担当するチーム(コードレビュー担当チーム)

それぞれのチームのメンバー構成としては、
コードレビュー担当チーム => そのプロダクトに関わる全てのエンジニア
リリース担当チーム => その中でも知識や経験が比較的あるエンジニア

という想定です。

このようにチームを2つ用意することによって、以下のようなメリットがあります。

* 商用ブランチ以外へのプルリクはすべてエンジニア全員が均等に担当するので、チーム内で実装に対する認識を共有しやすく、スキルの底上げも期待できる。
* 商用リリースの際は、経験のある人のレビューを通るので、不適切なコードをリリース前に見つけられる

Webhookの登録

次に、GitHubにあるレポジトリに対してWebhookを登録します。

y_スクリーンショット_2014-09-20_15_49_25.png

Payload URLにはHubotのURLを設定します。

例えば、以下のようなURLになります。

http://your-hubot-app.com/github/pullreq-auto-assign-webhook?channel_name=%23general&review_team_id=xxxxxxxx&release_team_id=yyyyy

ここで、パラメータとしてchannel_namereview_team_idrelease_team_idを渡しています。
これらのパラメータは、Hubotのスクリプト側で使います。
またSlackのChannel名ですが、#general#%23のようにエンコードして渡す必要があることに注意してください。

また、Webhookを送るトリガーを選ぶことができますが、今回は「Pull Request」のみにチェックを入れます。

これで、プルリクエストを作ったり、クローズしたりすると「Payload URL」で設定したURLにWebhookが送られるはずです。

Hubotスクリプトの実装

Webhookの設定が完了したので、今度はそれを受信する側のHubotスクリプトを用意します。
実際の実装は以下のようにしました(汚くてすいません)。


module.exports = (robot) ->

    github = require("githubot")(robot)

    owner = "qwintet-dev"# rewrite to your team name

    unless (url_api_base = process.env.HUBOT_GITHUB_API)?
        url_api_base = "https://api.github.com"

    #プルリクのレビュアーをランダムで選定
    _choosePullreqAssigneeAtRondom = (pullreq_author, team_id, callback) ->
        url = "#{url_api_base}/teams/#{team_id}/members"
        github.get url, (members, error) ->
            review_candidates = []
            for member, i in members
                member_name = member.login
                if member_name != pullreq_author
                    review_candidates.push member_name

            if review_candidates.length == 0
                robot.messageRoom channel_name, "there is no reviewer for \##{pullreq.number}"
            key = Math.floor Math.random() * review_candidates.length
            callback(review_candidates[key])


    #GitHubでのプルリクを自動アサイン
    robot.router.post "/github/pullreq-auto-assign-webhook", (req, res) ->

        data = req.body

        #openかreopen以外は何もしない
        if data.action not in ['opened', 'reopened']
            return res.end ""

        url = require('url')
        querystring = require('querystring')
        query = querystring.parse(url.parse(req.url).query)
        channel_name = query.channel_name

        review_team_id = query.review_team_id #GitHub上に作っているレビュー担当のチームのID
        relase_team_id = query.release_team_id #GitHub上に作っているリリース担当のチームのID

        pullreq = data.pull_request

        #既にassignされていたらそのまま
        if pullreq.assignee
            robot.messageRoom channel_name, "*\##{pullreq.number}* has already been assigned to *#{pullreq.assignee.login}*"
            return res.end ""

        repo = data.repository.name

        #assigneeを選定
        assign_team_id = if pullreq.base.ref == 'deployment/production' then relase_team_id else review_team_id #商用だけはリリース担当チームにアサイン、それ以外はレビューチームにアサイン
        author = pullreq.user.login
        _choosePullreqAssigneeAtRondom author, assign_team_id, (assignee) ->

            url = "#{url_api_base}/repos/#{owner}/#{repo}/issues/#{pullreq.number}"
            data = { "assignee": assignee }
            #assigeeを設定
            github.patch url, data, (issue, error) ->
                if !issue?
                    robot.messageRoom channel_name, "Error occured on pullreq \##{pullreq.number} auto assignment"
                    return
                body = "Assigned *\##{pullreq.number}* to *#{issue.assignee.login}*"
                robot.messageRoom channel_name, body
                res.end ""

GitHubからのプルリクエストのWebhookに反応して、自動アサインするHubotスクリプト

処理の大まかな流れとしては、以下のとおりです。
① Webhook側で設定したURL(/github/pullreq-auto-assign-webhook)に反応
② プルリクのアクションがopendかreopend以外は何もしない
③ 既にアサインされていたら何もしない
④ チームを指定して、ランダムで1人を選定(この時のチームは、プルリクのマージ先によって切り替える)
⑤ 選定された1人をアサインするようにGitHubAPIを叩く
⑥ アサイン完了したら、その旨をチャット(Slack)に通知

なお、このスクリプトはhubot-slack( https://github.com/tinyspeck/hubot-slack )がインストールされている必要がありますので、インストールされてない場合は、

npm install hubot-slack --save

でインストールしておいてください。

動作確認

これですべて設定完了したので、実際にプルリクエストを作ってみてください。
以下のように、作成時に自動でアサインされれば成功です。

y_スクリーンショット_2014-09-20_16_47_17.png