🔥

GithubのPRが作成されてから閉じるまでの時間を集計する

2024/07/29に公開

概要

開発チームでエンジニアリングマネージャーをしているいずりょーです。
今回は開発チームの開発生産性を計測しようとする中で、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で編集を提案
Sprocketテックブログ

Discussion