この記事の内容
GASを利用して、GitHubから放置されたままのブランチを探してSlackへ通知を行う実装を行いました
その過程と知見とかをまとめてます
完成版のコードは後半にまとめてますのでそちらを参照してください
はじめに
最近になってGitHubのbranchesというURLがあることに気がつきました
開発をしていると何となく実装してpushはしたけどPRできずに腐っているブランチというのはいくつかあると思います
以下はVuejsのリポジトリのbrachesですが、このようにactiveなものslateなものなどが可視化されていてとてもわかりやすいです
see: Viewing branches in your repository - User Documentation
腐っているブランチがあるかどうかはここを見にいけばよいのですが、どうせならSlackに通知してほしい
ぱっと探した感じ、GitHubではそれを通知するような機能は提供していなかったようなので、どうせならGASの入門ついでに通知Botを作ってみようということになりました
やったこと
GASでSlackにメッセージを送信する
【初心者向け】GASを使ってSlackへ自動通知 - Qiita を参考に実装しました
詳しいことはリンク先にあるので参照してください
GitHubからブランチの情報を取得してみる
github の grachqlを使ってデータを取得します
今回取得したいデータは、リポジトリにあるすべてのブランチとそれに紐づいた最後のコミット、さらに誰がコミットしたのか分かれば十分です
GraphQL API Explorer | GitHub Developer Guide でqueryを試すことができます!便利!!
ドキュメントはこちら GitHub GraphQL API v4 | GitHub Developer Guide
queryは以下のような感じになりました
query {
repository(owner: "Teamup-inc", name: "teamup360") {
name,
refs(first: 10, refPrefix:"refs/heads/") {
nodes {
name
target {
... on Commit {
history(first: 1) {
nodes {
messageHeadline
committedDate
url
author {
name
}
}
}
}
}
}
}
}
}
branchesがrefsになってますね、GitObjectに寄った名前になってるんですね
branchのすぐ下にcommitsがあるのかな?と思って試行錯誤していたので結構つまりました...
targetがあって ... on Commit
でアクセスして、そこのhistoryを参照するという結構面倒な感じでした
(... on Commit
って最初 ...なので何か省略しているのかな?と思い込んでスルーしてエラーになったのはないしょ)
その他は以下を参考に実装
Google Apps ScriptでGithub GraphQL API v4を叩く - Qiita
Branches on Repository - GraphQL API - GitHub Platform Forum
GitHub GraphQL repo commit history query
ブランチの情報からSlateなものを絞り込む
取得したデータのCommitDateを参照して3ヶ月以上経過したブランチを判別してみます
{
"name": "branch_name",
"target": {
"history": {
"nodes": [
{
"messageHeadline": "コミットメッセージ",
"committedDate": "2018-04-03T08:18:10Z",
"url": "コミットのURL",
"author": {
"name": "Author Name"
}
}
]
}
}
}
3ヶ月以上経過したかどうかはcommitedDateを参照すれば判別できそうです
ただ、jsで日付の比較を行うのは面倒です...
Moment.js使えないのかな?と思って調べたところ普通に外部ライブラリとして存在していたので導入してみました
日付&時刻の便利ライブラリ「Moment.js」をGoogle Apps Scriptで使う方法
ざっくりとこんな感じのコードで判定はできそうです
this.formatBranch = function(branch){
const commits = branch.target.history.nodes
return {
name: branch.name,
lastCommit: commits[0]
}
};
this.slateFilter = function(branch) {
// 最後のコミットから3ヶ月以上経過しているもの
// を取得する
const moment = Moment.moment
const today = moment()
const commitDate = moment(branch.lastCommit.committedDate)
const elapsedDate = today.diff(commitDate, 'days')
return elapsedDate > 30 * 3
}
Slackのメッセージを加工する
最後に取得したSlateなブランチをSlackへいい感じに通知しましょう
Slackへはattachmentsを利用することで結構リッチな通知を作成することが可能です
詳しくは以下を参照
SlackのIncoming Webhooksを使い倒す - Qiita
Message Formatting | Slack
環境変数
この手のコードを書くときに困るのがToken情報をハードコーディングしたくない問題です
一般的には環境変数が設定できるので、問題ないのですが、GASには環境変数ってあるの?ってなりました
適当にポチポチ探していたらスクリプトプロパティという項目を発見したので、調べてみるとまさに環境変数でした
【初心者向けGAS】スクリプトプロパティを操作してそのデータを取り出す方法 こちらを参考にToken情報を環境変数に設定してほぼ完了です
完成したもの
このスクリプトの実行頻度は週に1度くらいでよさそうなので、トリガーを設定します
GoogleAppsScriptのトリガーを使って自動化をマスターしよう | えびせん
おしまい
完成版のコード
シンタックスハイライトの問題で拡張子をjsにしているので書き写すときは .gsへ変更してください
function main() {
const slateBranches = new SlateBranches(PropertiesService.getScriptProperties().getProperty('GitHubAccessToken')).list()
const slackNotify = new SlackNotify(PropertiesService.getScriptProperties().getProperty('SlackWebhookUrl'))
const attachments = slateBranches.map(buildAttachment)
slackNotify.post(attachments)
}
function buildAttachment(branch) {
const commit = branch.lastCommit
const author = commit.author
return {
"color": "#cc3344",
"author_name": author.name,
"title": branch.name,
"title_link": 'https://github.com/:owner/:repo/compare/' + branch.name,
"text": commit.messageHeadline,
"footer": "GitHub last commit:",
"footer_icon": "https://assets-cdn.github.com/favicon.ico",
"ts": Moment.moment(commit.committedDate).unix()
}
}
function SlackNotify(postUrl) {
this.postUrl = postUrl
this.username = 'GASBot'
this.icon = ':robot_face:'
this._buildOptions = function(payload) {
const options = {
method: "post",
contentType: "application/json",
payload: payload
}
return options
}
this.post = function(message) {
const isAttachmens = Array.isArray(message)
const payload = JSON.stringify({
username: this.username,
icon_emoji: this.icon,
text: isAttachmens ? '' : message,
attachments: isAttachmens ? message : []
})
const options = this._buildOptions(payload)
UrlFetchApp.fetch(this.postUrl, options)
}
}
function SlateBranches(token) {
this.token = token
this.endpoint = "https://api.github.com/graphql"
this.query = 'query { \
repository(owner: ":owner", name: ":repo") { \
name, \
refs(first: 10, refPrefix:"refs/heads/") { \
nodes { \
name \
target { \
... on Commit { \
history(first: 1) { \
nodes { \
oid \
messageHeadline \
committedDate \
url \
author { \
name \
} \
} \
} \
} \
} \
} \
} \
} \
}'
this.list = function() {
const slateBanches = this._getBranches().
map(this._formatBranch).
filter(this._slateFilter)
return slateBanches
}
this._buildRequestOption = function() {
return {
method: "post",
contentType: "application/json",
headers: {
Authorization: 'bearer ' + this.token,
},
payload: JSON.stringify({ query: this.query }),
}
}
this._getBranches = function(){
const option = this._buildRequestOption();
const res = UrlFetchApp.fetch(this.endpoint, option)
const json = JSON.parse(res.getContentText())
return json.data.repository.refs.nodes
};
this._formatBranch = function(branch){
const commits = branch.target.history.nodes
return {
name: branch.name,
lastCommit: commits[0]
}
};
this._slateFilter = function(branch) {
// 最後のコミットから3ヶ月以上経過しているもの
// を取得する
const moment = Moment.moment
const today = moment()
const commitDate = moment(branch.lastCommit.committedDate)
const elapsedDate = today.diff(commitDate, 'days')
return elapsedDate > 30 * 3
}
}
その他の参考リンク: