GithubでコードレビューをチームにリクエストするSlack BotをSwift実装したお話🚢
サーバーサイドSwiftで代表的なフレームワークVaporを使って、チーム開発をしているレポジトリーでPull Requestを出した時に同じチーム内の誰かにランダムでレビューをアサインするSlack Botを実装しました!
職人のように洗練されて行って欲しいのでGithubShokuninと名付けました👍
GithubShokunin 1.0.0-beta.1完成した🎉https://t.co/TSrent6A3u
— yuzushioh (@yuzushioh) December 24, 2016
現状のGithubShokuninができること
現状のGithubShokuninができることは以下の一個だけです😇
- Slackで以下のようにメンションすることでGithub上でメンバーにランダムでReviewをリクエストできる。
@Bot review #PR Number
こんな感じです!
GithubShokuninのセットアップ
Vaporのインストール方法はReadMeにまとめてあります!
- Slack BotのTokenと名前を
bot-config.json
に書き込む
{
"token": "xoxo-2h4j43n5g3i2mn24g",
"bot_name": "GithubShokunin"
}
- GithubのTokenやTeamIdをセットする
※レポジトリー上でReviewを行うチームを作成する必要があります!
{
"owner_name": "yuzushioh",
"repo_name": "GithubShokunin",
"review_team_id": "12345",
"github_token": "biub3h24vb53vjjv2v42v24g(sample)"
}
Vaporについて
Vapor
インストール ReadMe
以下のコードで、Hello World!を実装できます!
let drop = Droplet()
drop.get("/") { request in
return "Hello World!"
}
drop.run()
GithubShokuninの実装
Hubotが使えないのでSlackのReal Time Messaging APIを使ってChatを監視しています。
VaporのWebSocket
以下のようにWebSocket Clientを作成できます。
import Engine
import WebSockets
try WebSocket.connect(to: url) { ws in
print("Connected to \(url)")
ws.onText = { ws, text in
print("[event] - \(text)")
}
ws.onClose = { ws, _, _, _ in
print("\n[CLOSED]\n")
}
}
メンションされたユーザーがbot-config.json
で指定したBotならば、そのチャットの内容に応じて処理を行うようになっています。
チャットの内容を元にContent
を作成する
現状はチャットの内容をもとに以下の3つにわけている。
enum MessageContent {
case requestReview(prNumber: String)
case commandLine
case unknown
}
Contentを元に必要なAPIを叩く
VaporのDropletを使ってGithubのAPIを叩く
let response = try drop.client.get("http://github.com", query: ["q": "test"])
ResponseのJSONをModelにマッピングする。
guard let json = response.json else {
throw BotError.invalidResponse
}
let pullRequest = try PullRequest(node: json.node)
作成したModelを元にBotのレスポンスを作成する
- SlackMessageというModelを用意している
struct SlackMessage {
let id: UInt32
let channel: String
let text: String
let type: String
init(to channel: String, text: String) {
self.id = UInt32.random()
self.channel = channel
self.text = text
self.type = "message"
}
}
このモデルはVaporのNodeConvertible
に準拠していて、モデルからNodeを作り出すことができます!
extension SlackMessage: NodeConvertible {
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"channel": channel,
"type": "message",
"text": text]
)
}
init(node: Node, in context: Context) throws {
id = UInt32.random()
channel = try node.extract("channel")
text = try node.extract("text")
type = try node.extract("type")
}
}
作成いたSlackMessageをメンションがあったSlack Channelに投げ返す!!
try WebSocket.connect(to: url) { ws in
print("Connected to \(url)")
ws.onText = { ws, text in
......
let response = SlackMessage(to: message.channel, text: responseText)
try socket.send(response)
}
ws.onClose = { ws, _, _, _ in
print("\n[CLOSED]\n")
}
}
## 思ったこと
- Hubotが使えないのでCoffee Scriptでかいた時より格段にコード量が増えてしまった。
- Vaporは初めて2週間くらいだがなかなか使いやすいし書きやすい!
Vaporの参考にしたり、是非チーム開発で使ってみてください!