🐙

Check! GitHub GraphQL で紐づいた pull requests と一緒に issues を取得するサンプルコード

2023/03/25に公開

Prologue

こんにちは、@dz_ こと、岩永かづみです。

GitHub の何らかの情報を取得するときは、REST API でもできますが、GraphQL を使うと必要なフィールドのみを指定して最小限の通信量で取得できるので効率的です。また、GraphQL は最初とっつきにくかったのですが、慣れれば決まったパターンで利用できるので、今では GraphQL を選択するようになりました。

そんな GitHub GraphQL API を使って、関連するプルリクエストと一緒に issues 一覧を取得する方法をご紹介します。

GitHub の GraphQL API の使い方については、こちらの記事がとても分かりやすいのでお勧めです!

https://zenn.dev/hsaki/articles/github-graphql

公式ドキュメントはこちらをご参照ください。

https://docs.github.com/en/graphql

GitHub GraphQL で issues と関連する pull requests を取得する

ポイントをかいつまんでご紹介します。サンプルコードの全体像は後述をご参考ください。

GraphQL クエリ

まず GraphQL のクエリについて、ページネーションなし/ありで紹介します。

ページネーションなし、個数固定

ページネーションを考慮せず、個数固定で取得する場合はこのようなクエリになります。

query ($repoName:String!, $owner:String!) {
  # 指定したリポジトリ
  repository(owner: $owner, name: $repoName) {
    # リポジトリの issues
    issues(first: 10) {
      nodes {
        number
        title
        createdAt
        closed
        closedAt
        # issue に関連づいたアイテム(イベント、コメントなど)のうち、
        # メンションで関連付けられたアイテム
        timelineItems(first: 10, itemTypes:CROSS_REFERENCED_EVENT) {
          nodes {
            ... on CrossReferencedEvent {
              source {
                # そのうち、プルリクエストであるアイテム
                ... on PullRequest {
                  number
                  title
                  merged
                }
              }
            }
          }
        }
      }
    }
  }
}

issue に紐づくアイテムは timelineItems というフィールドで取得します。さまざまな種類のアイテムが取得されるので、 itemTypes で絞って取得します。 itemTypesCROSS_REFERENCED_EVENT を指定すると、 CrossReferencedEvent のアイテムが取得できます。CrossReferencedEventsource に issue か pull request をもつので、 PullRequest を指定してプルリクエストを取得します。ドキュメントは下記をご参考ください。

ページネーションあり

ページネーションを考慮する場合、2段階に分けないと取得できません。(むしろ、できる方法がもしあったら教えて下さい!)

まず、issues の一覧を取得します。

query (
  $repoName: String!,
  $owner: String!,
  $issueCursor: String,
  $issueNumberInPage: Int!,
  ) {
  repository(owner: $owner, name: $repoName) {
    issues(first: $issueNumberInPage, after: $issueCursor) {
      nodes {
        number
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
}

レスポンスの repository.issues.pageInfo.hasNextPage には次にページがあるかどうかが返ってくるので、これを条件にループを回します。次のカーソルの位置は、レスポンスの repository.issues.pageInfo.endCursor で取得できます。

そして、issue の number ごとに timelineItems を取得します。ページネーションの仕組みは前述と同様です。

query (
  $repoName: String!,
  $owner: String!,
  $issueNumber: Int!,
  $timelineItemsCursor: String,
  $timelineItemsNumberInPage: Int!,
  ){
  repository(owner: $owner, name: $repoName) {
    issue(number: $issueNumber) {
      number
      title
      createdAt
      closed
      closedAt
      timelineItems(
        first: $timelineItemsNumberInPage,
        after: $timelineItemsCursor,
        itemTypes:CROSS_REFERENCED_EVENT
      ) {
        nodes {
          ... on CrossReferencedEvent {
            source {
              ... on PullRequest {
                number
                title
                merged
              }
            }
          }
        }
        pageInfo {
          endCursor
          hasNextPage
        }
      }
    }
  }
}

octokit で認証と GraphQL 実行を手軽に

さて、GitHub の API を利用するときは、Personal Access Token や GitHub App などを用いて認証を行わないとなりません。いろいろと面倒なので、ライブラリ octokit の利用をお勧めします。

今回のサンプルコードでは、JavaScript版 octokit を利用しています。

GraphQL のクエリを実行する場合は、下記のような形で、クエリと変数を渡す形で直感的に利用できます。

const issuesResponse = await octokit.graphql(issuesQuery, {
      owner,
      repoName,
      issueCursor: issuesCursor,
      issueNumberInPage: issuesNumberInPage,
    })

サンプルコード

サンプルコード全体はこちらに置いておきます。JavaScript でべた書きしています。

https://github.com/dzeyelid/github-learning-playground/tree/main/samples/aggregate-issues

https://github.com/dzeyelid/github-learning-playground/blob/main/samples/aggregate-issues/main.js

Epilogue

このサンプルコードは業務で検証用に書いたものです。クエリを作るときにちょっとてこずったので、自分への備忘もかねて共有です。

GraphQL は何回か書いてるうちに苦手意識がなくなりました。便利なので、ぜひみなさんも触ってみてください💡

Discussion