は開発活動の最重要データソース。Search API + API + GraphQL を組合せると PR 数・レビュー時間・コミット頻度・マージまでのリードタイム を時系列で取れます。
認証と PAT
PAT (Personal Access Token) を環境変数に
Bash
# Settings → Developer settings → Personal access tokens (classic) で作成# 必要なスコープ: repo (private含む), read:org
export GITHUB_TOKEN="ghp_xxxxxxxxxx"PR を一括取得 (REST API)
ある期間の PR を全件取得
Python
import os, requestsfrom datetime import datetime, timezone, timedelta
TOKEN = os.environ["GITHUB_TOKEN"]HEADERS = {"Authorization": f"Bearer {TOKEN}", "X-GitHub-Api-Version": "2022-11-28"}
def fetch_prs(org: str, repo: str, since: datetime) -> list[dict]: """指定リポの PR を since 以降で全件取得""" prs = [] page = 1 while True: r = requests.get( f"https://api.github.com/repos/{org}/{repo}/pulls", headers=HEADERS, params={"state": "all", "per_page": 100, "page": page, "sort": "updated", "direction": "desc"}, ) r.raise_for_status() data = r.json() if not data: break # since より古いものが出てきたら終了 if datetime.fromisoformat(data[-1]["updated_at"].replace("Z","+00:00")) < since: prs.extend([p for p in data if datetime.fromisoformat(p["updated_at"].replace("Z","+00:00")) >= since]) break prs.extend(data) page += 1 return prs
# 過去 30 日の PRsince = datetime.now(timezone.utc) - timedelta(days=30)prs = fetch_prs("hukuhuku-inc", "hukuhuku-co-jp", since)print(f"PR 件数: {len(prs)}")GraphQL でレビュー時間を取得
GraphQL: 1 PR のレビュー履歴を取得
Python
query = """query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $number) { title createdAt mergedAt author { login } reviews(first: 50) { nodes { author { login } state # APPROVED / CHANGES_REQUESTED / COMMENTED submittedAt } } } }}"""
def get_pr_with_reviews(owner: str, repo: str, number: int): r = requests.post( "https://api.github.com/graphql", headers=HEADERS, json={"query": query, "variables": {"owner": owner, "repo": repo, "number": number}}, ) return r.json()["data"]["repository"]["pullRequest"]主要メトリクスの計算
PR レベルのメトリクス算出
Python
import pandas as pd
def pr_metrics(pr: dict) -> dict: """1 PR のメトリクスを算出""" created = datetime.fromisoformat(pr["createdAt"].replace("Z", "+00:00")) merged = pr.get("mergedAt") and datetime.fromisoformat(pr["mergedAt"].replace("Z","+00:00")) reviews = pr.get("reviews", {}).get("nodes", []) first_review = min( (datetime.fromisoformat(r["submittedAt"].replace("Z","+00:00")) for r in reviews), default=None, ) return { "title": pr["title"], "author": pr["author"]["login"], "lead_time_hours": (merged - created).total_seconds() / 3600 if merged else None, "first_review_hours": (first_review - created).total_seconds() / 3600 if first_review else None, "review_count": len(reviews), "merged": merged is not None, }
# 100 PR 分の集計df = pd.DataFrame([pr_metrics(p) for p in prs_with_reviews])print(df.describe())print("\n人別の平均リードタイム:")print(df.groupby("author")["lead_time_hours"].mean().sort_values())レート制限と運用
GitHub API レート制限
REST: 5,000 req/h / トークン、GraphQL: 5,000 ポイント/h。組織規模が大きい場合は GitHub App で 15,000/h に引き上げ。ETag を使った If-None-Match キャッシュ + 必要な範囲だけ差分取得 で効率化。
ダッシュボードに載せる指標
- 週次 PR 作成数 / 人
- マージまでのリードタイム (中央値・p95)
- 初回レビュー応答時間 (低いほど良い、目安 24h 以内)
- レビューする側の負荷: 1 人あたり週レビュー数
- マージなし PR 比率 (オープンのまま放置されている割合)
- 変更行数の分布 (大きすぎる PR が多くないか)
次の話
EP.03 では / Anthropic API の usage を集計し、誰がどれだけ を使ったかを可視化します。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。