Skip to content

Commit 5c825a8

Browse files
Feat: Include top-level PR review summaries in output
- Adds functionality to fetch overall pull request reviews (e.g., approval/changes requested summaries). - Introduces `get_pull_request_reviews()` to call the relevant GitHub API endpoint. - In `main()`: - Fetches these overall reviews. - Filters them to exclude 'DISMISSED' states. - Applies the `--since` argument to filter reviews based on their `submitted_at` timestamp (client-side filtering). - Sorts the filtered reviews chronologically (oldest first). - Prints these overall review summaries under a new "# Overall Review Summaries" header before printing line-specific comments. - Output includes reviewer, submission time, state, review body (if any), and a link to the review. - Handles errors during the fetching of overall reviews separately. - Ensures that the `--since` argument continues to apply independently to line-specific comments based on their `updated_at` timestamp.
1 parent 666c3f6 commit 5c825a8

File tree

1 file changed

+113
-10
lines changed

1 file changed

+113
-10
lines changed

scripts/gha/get_pr_review_comments_standalone.py

Lines changed: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,36 @@ def list_pull_requests(token, state, head, base):
140140
return results
141141

142142

143+
def get_pull_request_reviews(token, owner, repo, pull_number):
144+
"""Fetches all reviews for a given pull request."""
145+
# Note: GitHub API for listing reviews does not support a 'since' parameter directly.
146+
# Filtering by 'since' must be done client-side after fetching all reviews.
147+
url = f'{GITHUB_API_URL}/pulls/{pull_number}/reviews'
148+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
149+
page = 1
150+
per_page = 100
151+
results = []
152+
keep_going = True
153+
while keep_going:
154+
params = {'per_page': per_page, 'page': page}
155+
page = page + 1
156+
keep_going = False
157+
try:
158+
with requests_retry_session().get(url, headers=headers, params=params,
159+
stream=True, timeout=TIMEOUT) as response:
160+
logging.info("get_pull_request_reviews: %s params: %s response: %s", url, params, response)
161+
response.raise_for_status()
162+
current_page_results = response.json()
163+
if not current_page_results:
164+
break
165+
results.extend(current_page_results)
166+
keep_going = (len(current_page_results) == per_page)
167+
except requests.exceptions.RequestException as e:
168+
logging.error(f"Error listing pull request reviews (page {params.get('page', 'N/A')}, params: {params}) for PR {pull_number} in {owner}/{repo}: {e}")
169+
return None # Indicate error
170+
return results
171+
172+
143173
def get_current_branch_name():
144174
"""Gets the current git branch name."""
145175
try:
@@ -344,25 +374,98 @@ def parse_repo_url(url_string):
344374
sys.stderr.write(f"{error_message}{error_suffix}\n")
345375
sys.exit(1)
346376

347-
sys.stderr.write(f"Fetching comments for PR #{pull_request_number} from {OWNER}/{REPO}...\n")
377+
# Fetch overall reviews first
378+
sys.stderr.write(f"Fetching overall reviews for PR #{pull_request_number} from {OWNER}/{REPO}...\n")
379+
overall_reviews = get_pull_request_reviews(args.token, OWNER, REPO, pull_request_number)
380+
381+
if overall_reviews is None:
382+
sys.stderr.write(f"Error: Failed to fetch overall reviews due to an API or network issue.{error_suffix}\nPlease check logs for details.\n")
383+
sys.exit(1)
384+
385+
filtered_overall_reviews = []
386+
if overall_reviews: # If not None and not empty
387+
for review in overall_reviews:
388+
if review.get("state") == "DISMISSED":
389+
continue
390+
391+
if args.since:
392+
submitted_at_str = review.get("submitted_at")
393+
if submitted_at_str:
394+
try:
395+
# Compatibility for Python < 3.11
396+
if sys.version_info < (3, 11):
397+
dt_str_submitted = submitted_at_str.replace("Z", "+00:00")
398+
else:
399+
dt_str_submitted = submitted_at_str
400+
submitted_dt = datetime.datetime.fromisoformat(dt_str_submitted)
401+
402+
since_dt_str = args.since
403+
if sys.version_info < (3, 11) and args.since.endswith("Z"):
404+
since_dt_str = args.since.replace("Z", "+00:00")
405+
since_dt = datetime.datetime.fromisoformat(since_dt_str)
406+
407+
# Ensure 'since_dt' is timezone-aware if 'submitted_dt' is.
408+
# GitHub timestamps are UTC. fromisoformat on Z or +00:00 makes them aware.
409+
if submitted_dt.tzinfo and not since_dt.tzinfo:
410+
since_dt = since_dt.replace(tzinfo=timezone.utc) # Assume since is UTC if not specified
411+
412+
if submitted_dt < since_dt:
413+
continue
414+
except ValueError as ve:
415+
sys.stderr.write(f"Warning: Could not parse review submitted_at timestamp '{submitted_at_str}' or --since timestamp '{args.since}': {ve}\n")
416+
# Decide: skip review or include it if parsing fails? For now, include.
417+
filtered_overall_reviews.append(review)
418+
419+
# Sort by submission time, oldest first
420+
try:
421+
filtered_overall_reviews.sort(key=lambda r: r.get("submitted_at", ""))
422+
except Exception as e: # Broad exception for safety
423+
sys.stderr.write(f"Warning: Could not sort overall reviews: {e}\n")
424+
425+
if filtered_overall_reviews:
426+
print("# Overall Review Summaries\n\n")
427+
for review in filtered_overall_reviews:
428+
user = review.get("user", {}).get("login", "Unknown user")
429+
submitted_at = review.get("submitted_at", "N/A")
430+
state = review.get("state", "N/A")
431+
body = review.get("body", "").strip()
432+
html_url = review.get("html_url", "N/A")
433+
review_id = review.get("id", "N/A")
434+
435+
print(f"## Review by: **{user}** (ID: `{review_id}`)\n")
436+
print(f"* **Submitted At**: `{submitted_at}`")
437+
print(f"* **State**: `{state}`")
438+
print(f"* **URL**: <{html_url}>\n")
439+
440+
if body:
441+
print("\n### Summary Comment:")
442+
print(body)
443+
print("\n---")
444+
# Add an extra newline to separate from line comments if any overall reviews were printed
445+
print("\n")
446+
447+
448+
sys.stderr.write(f"Fetching line comments for PR #{pull_request_number} from {OWNER}/{REPO}...\n")
348449
if args.since:
349-
sys.stderr.write(f"Filtering comments updated since: {args.since}\n")
450+
sys.stderr.write(f"Filtering line comments updated since: {args.since}\n") # Clarify this 'since' is for line comments
350451

351452
comments = get_pull_request_review_comments(
352453
args.token,
353454
pull_request_number,
354-
since=args.since
455+
since=args.since # This 'since' applies to line comment's 'updated_at'
355456
)
356457

357-
if comments is None: # Explicit check for None, indicating an API/network error
358-
sys.stderr.write(f"Error: Failed to fetch review comments due to an API or network issue.{error_suffix}\nPlease check logs for details.\n")
458+
if comments is None:
459+
sys.stderr.write(f"Error: Failed to fetch line comments due to an API or network issue.{error_suffix}\nPlease check logs for details.\n")
359460
sys.exit(1)
360-
elif not comments: # Empty list, meaning no comments found or matching filters
361-
sys.stderr.write(f"No review comments found for PR #{pull_request_number} (or matching filters).\n")
362-
return # Graceful exit with 0
461+
# Note: The decision to exit if only line comments fail vs. if only overall reviews fail could be nuanced.
462+
# For now, failure to fetch either is treated as a critical error for the script's purpose.
463+
464+
# Handling for empty line comments will be just before their processing loop.
465+
# if not comments: (handled later)
363466

364-
latest_activity_timestamp_obj = None
365-
processed_comments_count = 0
467+
latest_activity_timestamp_obj = None # This is for line comments' 'since' suggestion
468+
processed_comments_count = 0 # This tracks line comments
366469
print("# Review Comments\n\n")
367470
for comment in comments:
368471
created_at_str = comment.get("created_at")

0 commit comments

Comments
 (0)