更新日から1週間以上経過したPR(Pull Request)を自動的に閉じるGithubActionを作成しました。
なぜ作ったか
長期間放置されているPRがあり、スッキリさせたかったから。
期待される効果
PRの生存期限を設けることで以下の効果を期待しています。
問題 | 期待される効果 |
---|---|
属人化による、レビュワー不足によるPR放置 | ペアプロ |
PRの単位が大きくレビュワーへの負担よるPR放置 | 小さな単位でのPRを意識 |
タスクの優先度が変わることによるPR放置 | 今必要ないPRはClose |
作る
- リポジトリにある更新してからX日以上経ったPRをクローズする
- クローズしたPull Requestを標準出力で返す
作る前にGithubActionsを探してみましたが、なかったので作ります。
GitHub Marketplace · Actions to improve your workflow · GitHub
テンプレ
今回はTypeScriptのテンプレートを使わせていただきました。
テンプレートに加え、以下のライブラリを追加しています。
ライブラリ | 概要 |
---|---|
@octokit/rest | GithubのRestAPI Client |
jest-date-mock | Dateのモック(テストで使用) |
処理の流れ
入力: トークン(リポジトリのアクセストークン)
入力: 有効期限(デフォルトは7日)
↓
更新日から7日以上経過したPRを取得
↓
取得したプルリクエストをClose
↓
出力: メッセージ
出力: プルリクエストのリスト(Json.stringfyで文字列で出力)
作成中に悩んだ箇所
テストでモックが効かない?
child_process.execSync でNodeからshellコマンドを実行できるんですが、以下のようなモックを使ったテストは失敗します。
test('test runs', async () => {
jest.spyOn(githubService, 'getDeadlinePullRequest').mockImplementation(() => {
return Promise.resolve([
{
number: 1,
title: 'テスト1',
htmlUrl: 'http://test1.com'
},
])
})
jest.spyOn(githubService, 'closePullRequests').mockImplementation(() => {
return Promise.resolve()
})
const ip = path.join(__dirname, '..', 'lib', 'main.js')
const options: cp.ExecSyncOptions = {
env: process.env
}
// Command failed: node xxx/main.js
console.log(cp.execSync(`node ${ip}`, options).toString())
})
xxx/main.jsを実際に叩いているので、jestのモックは無効となります。
配列の出力について
setOutputの第二引数はstring
なので、配列を返すためJSON.stringify
で文字列にして出力した後、使う側でparseして使うことを考えました。
core.setOutput('closedPulls', JSON.stringify(deadlinePullList))
ただ使う側のSlack Notifyではそのような入力は受け付けていません。
また、GithubActionにもループ処理の構文がありませんでした。
仕方なくCloseしたPull Requestのリストをメッセージとして結合して返すことにしました。
let message = "";
for (const pull of deadlinePullList) {
message += `* [${pull.title}](${pull.htmlUrl})\n`
}
core.setOutput('closedPulls', JSON.stringify(deadlinePullList))
core.setOutput('message', message)
使用例
毎日09:00にcronで実行。クローズされたPRがあればSlackに通知。
name: Close PR by deadline and slack notification
on:
schedule:
# UTC
- cron: '0 0 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Close PR by deadline
uses: matometaru/pr-deadline@1.0.0
with:
token: ${{ secrets.PR_TOKEN }}
expirationDate: 7
id: prDeadline
- name: Slack Notification
if: steps.prDeadline.outputs.closedPulls != '[]'
uses: rtCamp/action-slack-notify@master
env:
SLACK_CHANNEL: self
SLACK_ICON: https://github.com/rtCamp.png?size=48
SLACK_MESSAGE: ${{ steps.prDeadline.outputs.message }}
SLACK_TITLE: pr-deadline
SLACK_USERNAME: rtCamp
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
参考
https://help.github.com/ja/actions/reference/context-and-expression-syntax-for-github-actions
https://octokit.github.io/rest.js/v17