GitHub上のPRから開発生産性を測ろうと思ったらとりあえず叩くGraphQL

# はじめに
[LIFULL Advent Calendar 2024](https://qiita.com/advent-calendar/2024/lifull) 7日目の記事です。
界隈では多様な手法でエンジニアの開発生産性を計測し、向上させようという動きは珍しくありません。
弊社でも複数の活動ログから生産性計測を行っていますが、まずはやはりGitHub上での活動を分析するのが定石かと思います。
とはいえGitHubから得られる情報は膨大で、GraphQLのオブジェクト仕様を見ながらいちいち試すのも大変です。
そこで、はじめの一歩として一旦これだけ取っておけばよいのでは？と思う情報をピックアップしたGraphQLを共有します。

# GraphQL
項目の説明は後述するとして、GraphQL全体は以下です。
超ザックリ説明すると、Organization配下の全リポジトリに対してマージ済みPRの情報を期間指定で取得します。
※1日1回、前日のデータを取得して蓄積することを想定しています。
```graphql
query {
  search(query: "type:pr is:merged org:YOUR_ORG_NAME merged:YYYY-MM-DDT00:00:00+09:00..YYYY-MM-DDT23:59:59+09:00", type: ISSUE, first: 100, after: "PrevPage_endCursor") {
    pageInfo {
      hasNextPage
      endCursor
    }
    issueCount
    nodes {
      ... on PullRequest {
        id
        repository {
          name
        }
        title
        createdAt
        mergedAt
        author {
          login
        }
        url
        baseRefName
        headRefName
        changedFiles
        additions
        deletions
        commits(first:1) {
          totalCount
          nodes {
            commit {
              authoredDate
            }
          }
        }
        timelineItems(itemTypes:READY_FOR_REVIEW_EVENT,last:1){
          nodes {
            ... on ReadyForReviewEvent {
              createdAt
            }
          }
        }
        reviews(first:1) {
          totalCount
          nodes {
            createdAt
            state
            author {
              login
            }
          }
        }
        latestOpinionatedReviews(first:1) {
          nodes {
            createdAt
            state
            author {
              login
            }
          }
        }
      }
    }
  }
}
```
## search
PR情報取得するクエリは多様ですが、今回はsearchを使います。
queryの記法について詳しくは[こちら](https://docs.github.com/ja/search-github/searching-on-github/searching-issues-and-pull-requests)をご参照ください。
```graphql
search(query: "type:pr is:merged org:{ORG_NAME} merged:YYYY-MM-DDT00:00:00+09:00..YYYY-MM-DDT23:59:59+09:00", type: ISSUE, first: 100, after: {endCursor}) {}
```
| query |説明|
|:-:|:-:|
|type:pr|PullRequestに限定する|
|is:merged|マージ済みのものに限定する|
|org:YOUR_ORG_NAME|Organizationを指定する|
|merged:YYYY-MM-DDT00:00:00+09:00..YYYY-MM-DDT23:59:59+09:00|PRがマージされた期間を指定する。（日本時間で指定するためにYYYY-MM-DDT00:00:00+09:00の形式を使う）|


## pageInfo
ページング処理のための情報です。
| FIELDS | 説明 |
|:-:|:-:|
|hasNextPage|次ページの有無|
|endCursor|次ページを取得する際上記のsearch「after:」句で指定します|

## ... on PullRequest
PRに含まれる情報です。
|FIELDS|説明|
|:-:|:-:|
|repository{name}|リポジトリ名|
|title|PRタイトル|
|createdAt|PR作成日時|
|mergedAt|PRマージ日時|
|author{login}|PR作成者|
|url|URL|
|baseRefName|マージ先ブランチ名|
|headRefName|マージ元ブランチ名|
|changedFiles|変更ファイル数|
|additions|追加行数|
|deletions|削除行数|
|commits(first:n)|コミット情報（最初のコミットを取得するためn=1を指定,totalCountでPRに含まれるコミット総数を取得）|
|timelineItems(itemTypes:READY_FOR_REVIEW_EVENT,last:n)|「Ready for review」イベントの情報（Draft PR利用時）|
|reviews(first:n)|レビュー情報（最初のレビューを取得するためn=1を指定,totalCountでPRに含まれるレビュー総数を取得）|
|latestOpinionatedReviews(first:n)|レビュワー毎の最新のレビュー情報（state=APPROVEDの情報を取得するために利用。n数は自由）|

## 注意点
Organization管理ルールやAPI実行者の権限よって取得できる情報が変わってきます。
owner権限ユーザで実行すれば全て取得できるでしょうが、そうでない場合ご自身の欲しい情報が得られるか、権限周りの確認をしてみてください。

# 何が測れるのか
上記の情報を定期的に取得することで、以下のような集計、分析が可能になります。
* 期間単位でのPR数の集計
    * authorやrepositoryあるいはbranchで絞ることも可能
    * authorを組織やチームで紐づけることにより組織単位での計測も可能
* PRサイズ
    * 変更行数や変更ファイル数、あるいはコミット数からPRのボリュームが推し測れます
* サイクルタイム
    * 開発フローによって多少順番が前後するかもしれませんが以下の順にタイムスタンプが得られるため、開発フロー上で時間がかかっている点を分析できます
        1. 最初のコミット時間
        2. PR作成時間
        3. レビュー可能になった（Ready for review）時間
        4. 最初にレビューが付いた時間
        5. レビューがAPPROVEDされた時間
        6. PRがマージされた時間

# 最後に
弊社ではこのような情報をBigQueryに投入して分析していますが
気軽に始めるのであれば

GASでAPI実行 → Googleスプレッドシートに蓄積 → LookerStudioで分析

がオススメです。