🔥
GithubのPRが作成されてから閉じるまでの時間を集計する
概要
開発チームでエンジニアリングマネージャーをしているいずりょーです。
今回は開発チームの開発生産性を計測しようとする中で、Githubのプルリクエスト(以下PR)が作成されてからマージされるまでの時間を計測したいなーとなったので、GithubGraphQLAPIを使って、指定した期間のPRを取得して、それらが作成されてからマージされるまでの時間の平均を取得するスクリプトを作成しました。
必要なもの
- GithubへアクセスするToken
- 今回はPersonal Access Tokenを使っていますが、それ以外の方法の場合は認証する部分を置き換えてください
簡単に仕様をまとめる
- コマンドで実行できる
- リポジトリ名と集計する期間をオプションで渡せる
- 指定された期間のPRを取得する
- 取得したPRの作成時間とマージされた時間からPRがオープンされていた時間の平均を出力する
コマンドで実行できる / リポジトリ名と集計する期間をオプションで渡せる
- node-getoptを利用します
- npxで実行してオプションを渡して利用するイメージ
- こんな感じでオプションの設定ができます
import Getopt from "node-getopt";
const getopt = new Getopt([
["r", "repository", "target repository name"],
["s", "start_date=YYYY-MM-DD", "start date (YYYY-MM-DD)"],
["e", "end_date=YYYY-MM-DD", "end date (YYYY-MM-DD)"],
["h", "help", "display this help"],
]).bindHelp();
const opt = getopt.parseSystem();
指定された期間のPRを取得する
- GithubGraphQLAPIには期間を指定してPRを取得するエンドポイントがないので、作成された時間の降順でPRを50件ずつ取得して、期間内のPRを保持しつつ、期間外になるまで呼び出す再帰関数を実装しました
const getPullReqeusts = async (args?: {
pullRequests: GetPullRequestsResult["repository"]["pullRequests"]["nodes"];
continueInfo: {
isContinue: boolean;
nextCursor: string;
};
}): Promise<GetPullRequestsResult["repository"]["pullRequests"]["nodes"]> => {
if (args && !args.continueInfo.isContinue) {
return args.pullRequests;
}
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const query = `
query (
$repoName: String!,
$owner: String!
){
repository(owner: $owner, name: $repoName) {
pullRequests(first: 50, orderBy: {field: CREATED_AT, direction: DESC}, states: [MERGED]) {
nodes {
number
title
url
createdAt
updatedAt
mergedAt
author {
login
}
assignees (first: 5) {
nodes {
login
}
}
additions
deletions
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}
}
`;
const { repository } = await octokit.graphql<GetPullRequestsResult>(query, {
owner: "sprocket-inc",
repoName: repositoryName,
});
const { nodes, pageInfo } = repository.pullRequests;
const pullRequests = args?.pullRequests ?? [];
nodes.forEach((n) => {
if (
isBefore(startDate, new Date(n.createdAt)) &&
isAfter(endDate, new Date(n.createdAt))
) {
pullRequests.push(n);
}
});
const isContinue = isBefore(
startDate,
new Date(nodes[nodes.length - 1].createdAt)
);
return await getPullReqeusts({
pullRequests,
continueInfo: {
isContinue,
nextCursor: pageInfo.endCursor,
},
});
};
queryの説明
- ここで取得しているPRのデータは以下です
- 詳しくはこちらに書いてあります
項目名 | 何か |
---|---|
nodes.number | PR番号 |
nodes.title | タイトル |
nodes.url | PRのURL |
nodes.createdAt | 作成時刻 |
nodes.updatedAt | 最終更新時刻 |
nodes.mergedAt | マージされた時刻 |
nodes.author.login | PR作成者 |
nodes.assignees.nodes.login | アサインされたアカウント |
nodes.additions | 追加された行数 |
nodes.deletions | 削除された行数 |
pageInfo.startCursor | 取得したPRの1番目のCursor |
pageInfo.endCursor | 取得したPRの最後のCursor |
pageInfo.hasNextPage | 次のページがあるか |
pageInfo.hasPreviousPage | 前のページがあるか |
取得したPRの作成時間とマージされた時間からPRがオープンされていた時間の平均を出力する
- 先ほど取得したPRを forEach で回しながら create → merge までの時間を取得していきます
- 集計する単位は「日」と「時間」にしています
const { repository } = await octokit.graphql<GetPullRequestsResult>(query, {
owner: "sprocket-inc",
repoName: repositoryName,
});
const { nodes, pageInfo } = repository.pullRequests;
const pullRequests = args?.pullRequests ?? [];
nodes.forEach((n) => {
if (
isBefore(startDate, new Date(n.createdAt)) &&
isAfter(endDate, new Date(n.createdAt))
) {
pullRequests.push(n);
}
});
const isContinue = isBefore(
startDate,
new Date(nodes[nodes.length - 1].createdAt)
);
return await getPullReqeusts({
pullRequests,
continueInfo: {
isContinue,
nextCursor: pageInfo.endCursor,
},
});
実際のコード
コードはこちら
まとめ
GithubGraphQLAPIを使ってPRを取得するスクリプトを実装しました。
これでスクリプトを実行するだけで指定した期間のPRが作成されてマージされるまでの時間の平均を取得することができるようになりました。
ここから複数のリポジトリのPRをまとめて集計できるようにしたり、さらに他のデータを集計できるような拡張をしていこうと考えています。
GitHubで編集を提案
Discussion