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

#概要 / 目的

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

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

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

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

* [Slack / Hubot / GitHub / CircleCI によるChatOpsなデプロイ方法](http://qiita.com/s-kiriki/items/26bdf537169891b22653)
* [ChatOps時のブランチ運用戦略](http://qiita.com/s-kiriki/items/194d266e0de070131a25)

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

![Untitled(1).png](https://qiita-image-store.s3.amazonaws.com/0/47881/5acb9f79-c283-eee1-023e-2e841214a406.png)


#設定 / 実装

##GitHubチームの作成

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

![y_スクリーンショット_2014-09-20_15_45_26.png](https://qiita-image-store.s3.amazonaws.com/0/47881/07497369-2824-2e4f-dcc5-d1d832563ca8.png)


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

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

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

という想定です。

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

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


##Webhookの登録

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


![y_スクリーンショット_2014-09-20_15_49_25.png](https://qiita-image-store.s3.amazonaws.com/0/47881/8a6b6bd5-531c-5347-9c94-cd14b28b4b20.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_name`と`review_team_id`と`release_team_id`を渡しています。
これらのパラメータは、Hubotのスクリプト側で使います。
またSlackのChannel名ですが、`#general`の`#`を`%23`のようにエンコードして渡す必要があることに注意してください。


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

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

##Hubotスクリプトの実装

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

```coffeescript

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以外は何もしない
③ 既にアサインされていたら何もしない
④ チームを指定して、ランダムで１人を選定（この時のチームは、プルリクのマージ先によって切り替える）
⑤ 選定された１人をアサインするようにGitHubAPIを叩く
⑥ アサイン完了したら、その旨をチャット(Slack)に通知



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

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

##動作確認

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

![y_スクリーンショット_2014-09-20_16_47_17.png](https://qiita-image-store.s3.amazonaws.com/0/47881/3e99295d-687c-579e-9d2c-8d66b69ac15b.png)