diff --git a/scripts/pr_report/pr_dashboard.py b/scripts/pr_report/pr_dashboard.py index 4dc50b6af..5096f030a 100644 --- a/scripts/pr_report/pr_dashboard.py +++ b/scripts/pr_report/pr_dashboard.py @@ -12,18 +12,24 @@ scripts/pr_report/github_mentions.txt with GitHub usernames for @mentions. """ +import base64 import html +import io import json -import math import os import sys import time import urllib.error import urllib.parse import urllib.request -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from pathlib import Path +import matplotlib + +matplotlib.use("Agg") # Non-interactive backend — must be set before importing pyplot +import matplotlib.pyplot as plt + API = "https://api.github.com" ACCEPT = "application/vnd.github+json" @@ -254,20 +260,163 @@ def classify_check_runs(check_runs): return results -# ── Pie chart helper ────────────────────────────────────────────────────────── +# ── Fetch recently closed/merged PRs ───────────────────────────────────────── + + +def fetch_recent_closed_prs(owner, repo, token, days=10): + """ + Fetch PRs that were closed (merged or not) within the last `days` days. + Uses the GitHub search API to find PRs closed in the date range. + Returns a list of PR dicts (lightweight — only fields available in list endpoint). + """ + cutoff = datetime.now(timezone.utc) - timedelta(days=days) + out = [] + page = 1 + while True: + params = { + "state": "closed", + "sort": "updated", + "direction": "desc", + "per_page": 100, + "page": page, + } + chunk, headers = gh_request(f"/repos/{owner}/{repo}/pulls", token, params) + if not chunk: + break + + stop_after_page = False + for pr in chunk: + closed_at_str = pr.get("closed_at") + if closed_at_str and parse_iso(closed_at_str) >= cutoff: + out.append(pr) + + # If the oldest `updated_at` in this page is before the cutoff, no + # further pages can contain recently-closed PRs. + if chunk: + oldest_updated = min(parse_iso(pr["updated_at"]) for pr in chunk if pr.get("updated_at")) + if oldest_updated < cutoff: + stop_after_page = True + + if stop_after_page or 'rel="next"' not in (headers.get("Link") or ""): + break + page += 1 + + return out + + +# ── Trend chart helper ──────────────────────────────────────────────────────── -def generate_pie_chart_svg(author_counts): +def generate_trend_chart_png(pulls_open, pulls_closed, now, days=10, png_path=None): """ - Generate a self-contained inline SVG pie chart showing PR distribution - by author. Returns an HTML string (a