【Mac】GitHubのPull Request情報の取得

# モチベーション
GitHubのPull Request（PR）を作成してレビューが沢山あったりしたとき、レビュー対応ステータスを把握したいと思い、作ってみた

# 手順
## 事前準備
1. requestsをinstall
    1. `pip install requests`を実行
1. [Personal access tokens (classic)](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#personal-access-token-classic-%E3%81%AE%E4%BD%9C%E6%88%90)を作成する
    1. Select scopesではとりあえず`repo`だけあれば大丈夫
1. 環境変数に作成したtokenを設定する
    1. terminal上で`export GITHUB_TOKEN='ghp_xxxx'`を実行

## 実行
以下コードの`python get_commit_info.py`を実行
```python:get_commit_info
import os
import requests
import re
import csv

# -----------------------------
# 設定（必要に応じて書き換える）
# -----------------------------
# https://github.com/OWNER/REPO/pull/PR_NUMBER
OWNER = "foo"
REPO = "bar"
PR_NUMBER = baz
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")

# -----------------------------
# ヘッダー設定
# -----------------------------
HEADERS = {
    "Authorization": f"Bearer {GITHUB_TOKEN}",
    "Accept": "application/vnd.github+json"
}

# -----------------------------
# GraphQLクエリでレビューコメントのステータス取得
# -----------------------------
# https://docs.github.com/ja/graphql/reference/objects
def get_review_threads(owner, repo, pr_number):
    query = """
    query($owner: String!, $name: String!, $number: Int!) {
      repository(owner: $owner, name: $name) {
        pullRequest(number: $number) {
          reviewThreads(first: 100) {
            nodes {
              isResolved
              comments(first: 1) {
                nodes {
                  bodyText
                  author { login }
                  createdAt
                }
              }
            }
          }
        }
      }
    }
    """
    variables = {
        "owner": owner,
        "name": repo,
        "number": pr_number
    }

    response = requests.post(
        "https://api.github.com/graphql",
        headers=HEADERS,
        json={"query": query, "variables": variables}
    )

    res_json = response.json()

    if "errors" in res_json:
        raise Exception(f"GraphQL error: {res_json['errors']}")

    pull_request = res_json.get("data", {}).get("repository", {}).get("pullRequest")
    if pull_request is None:
        raise Exception("PRが見つかりません。OWNER, REPO, PR_NUMBERを確認してください。")

    return pull_request["reviewThreads"]["nodes"]


# -----------------------------
# CSVファイル出力
# -----------------------------
def write_review_status_to_csv(threads, filename="review_status.csv"):
    with open(filename, mode='w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(["No", "Status", "Commentor", "CreatedAt", "Comment"])

        for i, thread in enumerate(threads, 1):
            status = "Resolved" if thread["isResolved"] else "Unresolved"
            comment = thread["comments"]["nodes"][0]
            commentor = comment["author"]["login"]
            created = comment["createdAt"]
            body = re.sub(r'@.*\n', '', comment["bodyText"]).strip()
            print(f"{i:02d}. {status} \t {commentor} \t {created} \t {body}")
            writer.writerow([i, status, commentor, created, body])

    print(f"{filename} に出力しました。")


# -----------------------------
# 実行
# -----------------------------
if __name__ == "__main__":
    if not GITHUB_TOKEN:
        raise EnvironmentError("GITHUB_TOKEN が設定されていません。環境変数に追加してください。")
    try:
        threads = get_review_threads(OWNER, REPO, PR_NUMBER)
        if not threads:
            print("レビューコメントスレッドが見つかりません。")
        else:
            write_review_status_to_csv(threads)
    except Exception as e:
        print(f"エラーが発生しました: {e}")

```

# 結果
こんな感じのCSVが吐き出されます
![スクリーンショット 2025-05-28 19.18.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/656963/0fff2b77-56a3-4211-ac5f-03bd93e2052d.png)

# 感想
コメントみてどういうコードが良い悪いかのような、分析にも活かせるかもしれない。色んなご意見お待ちしてます。