開発チームの健全性とかを定量的に計測する指標としてFour Keysがあると思うのですが、この辺の変更のリードタイム
を表す数値として厳密では無いですが、PRが作成されてからクローズされるまでの時間を簡単に計測できないかなと思い調べてみたら結構すぐ取れそうだったのでメモ🗒
GitHub上のPRがクローズされるまでの時間を計測する
やり方は簡単でGitHubのAPIでPRのリストを取得できるので、それを使って取得してPRの作成日時とクローズされた日時の差分を計算します。 ※ページングとかを考慮すると実装が複雑になるのと全部のPRを計算しなくても良いのでparametersで指定可能な直近100件で計測します。
その後は好きに計算すればいいのですが、今回はパーセンタイルを指定して出力するようにしてみました。
以下はOktokit
を使って実装してみたサンプルです。
const { Octokit } = require("@octokit/core"); const repoOwner = "owner name"; const repoName = "repo name"; const repoType = "private or public"; const percentile = 50; const baseBranch = "master"; const token = process.env.GITHUB_TOKEN; // NOTE: initialize OktoKit const octokit = new Octokit({ auth: token }); // main const dateHourDiff = (before, after) => (after - before) / 1000 / 60 / 60; const getPulls = async () => { return await octokit.request( "GET /repos/{owner}/{repo}/pulls?state=closed&per_page=100&sort=created&direction=desc&base={base}", { owner: repoOwner, repo: repoName, type: repoType, base: baseBranch, } ); }; try { (async () => { const res = await getPulls(); const result = res.data.map((pr) => { return { number: pr.number, title: pr.title, created_at: pr.created_at, merged_at: pr.merged_at, closed_at: pr.closed_at, duration_hour: dateHourDiff( new Date(pr.created_at), new Date(pr.closed_at) ), }; }); durations = result.map((r) => r.duration_hour).sort(); percentile_index = Math.floor(durations.length * (percentile / 100.0)); console.log("durations", durations[percentile_index]); })(); } catch (error) { console.error(error); }
実行すると以下のような感じです。
$ node index.js durations 32.27638888888889
APIの詳細な使用は以下に記載されています。
Github Actionにしてみる
任意のタイミングでリポジトリ上から計測できると便利そうなので以下を参考にGitHub Actionにしてみます。
const core = require("@actions/core"); const github = require("@actions/github"); // NOTE: configure parameters const repoOwner = github.context.repo.owner; const repoName = github.context.repo.repo; const repoType = core.getInput("repo-type"); const percentile = core.getInput("percentile"); const baseBranch = core.getInput("base-branch"); const token = core.getInput("repo-token"); // NOTE: initialize OktoKit const octokit = github.getOctokit(token); // main const dateHourDiff = (before, after) => (after - before) / 1000 / 60 / 60; const getPulls = async () => { return await octokit.request( "GET /repos/{owner}/{repo}/pulls?state=closed&per_page=100&sort=created&direction=desc&base={base}", { owner: repoOwner, repo: repoName, type: repoType, base: baseBranch, } ); }; try { (async () => { const res = await getPulls(); const result = res.data.map((pr) => { return { number: pr.number, title: pr.title, created_at: pr.created_at, merged_at: pr.merged_at, closed_at: pr.closed_at, duration_hour: dateHourDiff( new Date(pr.created_at), new Date(pr.closed_at) ), }; }); const durations = result.map((r) => r.duration_hour).sort((a, b) => a - b); const percentile_index = Math.floor(durations.length * (percentile / 100.0)); core.setOutput("duration", durations[percentile_index]); })(); } catch (error) { core.setFailed(error.message); }
使うには以下のような感じです。
on: workflow_dispatch jobs: pr_close_duration: runs-on: ubuntu-latest name: A job calc close PR duration steps: - name: Checkout uses: actions/checkout@v2 - name: run pr-close-duration uses: ./pr-close-duration id: pr-close-duration with: base-branch: main repo-type: private percentile: 50 repo-token: ${{ secrets.MY_GITHUB_TOKEN }} - name: Get the output time run: echo "The duration was ${{ steps.pr-close-duration.outputs.duration }} hour."
以下のような感じで見れます。
GitHub Actionは以下で公開してみました。
おしまい。