Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 50 additions & 52 deletions .github/update_release_pr.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from os import getenv
from typing import Optional

import requests


def get_github_prs(token: str, owner: str, repo: str, label: str = "", state: str = "all") -> list[dict]:
"""
Fetches pull requests from a GitHub repository that match a given milestone and label.
Fetches pull requests from a GitHub repository that match a given label and state.

Args:
token (str): GitHub token.
Expand All @@ -20,42 +21,13 @@
"""
headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json",

Check warning on line 24 in .github/update_release_pr.py

View workflow job for this annotation

GitHub Actions / Check Spelling

`vnd` is not a recognized word. (unrecognized-spelling)
}

milestone_id = None
milestone_url = f"https://api.github.com/repos/{owner}/{repo}/milestones"
params = {"state": "open"}

try:
response = requests.get(milestone_url, headers=headers, params=params)
response.raise_for_status()
milestones = response.json()

if len(milestones) > 2:
print("More than two milestones found, unable to determine the milestone required.")
exit(1)

# milestones.pop()
for ms in milestones:
if ms["title"] != "Future":
milestone_id = ms["number"]
print(f"Gathering PRs with milestone {ms['title']}...")
break

if not milestone_id:
print(f"No suitable milestone found in repository '{owner}/{repo}'.")
exit(1)

except requests.exceptions.RequestException as e:
print(f"Error fetching milestones: {e}")
exit(1)

# This endpoint allows filtering by milestone and label. A PR in GH's perspective is a type of issue.
# This endpoint allows filtering by label(and milestone). A PR in GH's perspective is a type of issue.
prs_url = f"https://api.github.com/repos/{owner}/{repo}/issues"
params = {
"state": state,
"milestone": milestone_id,
"labels": label,
"per_page": 100,
}
Expand Down Expand Up @@ -83,14 +55,18 @@
return all_prs


def get_prs(pull_request_items: list[dict], label: str = "", state: str = "all") -> list[dict]:
def get_prs(
pull_request_items: list[dict], label: str = "", state: str = "all", milestone_title: Optional[str] = None
) -> list[dict]:
"""
Returns a list of pull requests after applying the label and state filters.

Args:
pull_request_items (list[dict]): List of PR items.
label (str): The label name. Filter is not applied when empty string.
state (str): State of PR, e.g. open, closed, all
milestone_title (Optional[str]): The milestone title to filter by. This is the milestone number you created
in GitHub, e.g. '1.20.0'. If None, no milestone filtering is applied.

Returns:
list: A list of dictionaries, where each dictionary represents a pull request.
Expand All @@ -99,22 +75,32 @@
pr_list = []
count = 0
for pr in pull_request_items:
if state in [pr["state"], "all"] and (not label or [item for item in pr["labels"] if item["name"] == label]):
pr_list.append(pr)
count += 1
if state not in [pr["state"], "all"]:
continue

if label and not [item for item in pr["labels"] if item["name"] == label]:
continue

print(f"Found {count} PRs with {label if label else 'no filter on'} label and state as {state}")
if milestone_title:
if pr["milestone"] is None or pr["milestone"]["title"] != milestone_title:
continue

pr_list.append(pr)
count += 1

print(
f"Found {count} PRs with {label if label else 'no filter on'} label, state as {state}, and milestone {pr["milestone"] if pr["milestone"] is not None else "None"}"
)

return pr_list

def get_prs_assignees(pull_request_items: list[dict], label: str = "", state: str = "all") -> list[str]:

def get_prs_assignees(pull_request_items: list[dict]) -> list[str]:
"""
Returns a list of pull request assignees after applying the label and state filters, excludes jjw24.
Returns a list of pull request assignees, excludes jjw24.

Args:
pull_request_items (list[dict]): List of PR items.
label (str): The label name. Filter is not applied when empty string.
state (str): State of PR, e.g. open, closed, all
pull_request_items (list[dict]): List of PR items to get the assignees from.

Returns:
list: A list of strs, where each string is an assignee name. List is not distinct, so can contain
Expand All @@ -123,13 +109,13 @@
"""
assignee_list = []
for pr in pull_request_items:
if state in [pr["state"], "all"] and (not label or [item for item in pr["labels"] if item["name"] == label]):
[assignee_list.append(assignee["login"]) for assignee in pr["assignees"] if assignee["login"] != "jjw24" ]
[assignee_list.append(assignee["login"]) for assignee in pr["assignees"] if assignee["login"] != "jjw24"]

print(f"Found {len(assignee_list)} assignees with {label if label else 'no filter on'} label and state as {state}")
print(f"Found {len(assignee_list)} assignees")

return assignee_list


def get_pr_descriptions(pull_request_items: list[dict]) -> str:
"""
Returns the concatenated string of pr title and number in the format of
Expand Down Expand Up @@ -168,7 +154,7 @@
"""
headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json",

Check warning on line 157 in .github/update_release_pr.py

View workflow job for this annotation

GitHub Actions / Check Spelling

`vnd` is not a recognized word. (unrecognized-spelling)

Check warning on line 157 in .github/update_release_pr.py

View workflow job for this annotation

GitHub Actions / Check Spelling

`vnd` is not a recognized word. (unrecognized-spelling)
"Content-Type": "application/json",
}

Expand Down Expand Up @@ -207,30 +193,42 @@

print(f"Fetching {state} PRs for {repository_owner}/{repository_name} ...")

pull_requests = get_github_prs(github_token, repository_owner, repository_name)
# First, get all PRs to find the release PR and determine the milestone
all_pull_requests = get_github_prs(github_token, repository_owner, repository_name)

if not pull_requests:
print("No matching pull requests found")
if not all_pull_requests:
print("No pull requests found")
exit(1)

print(f"\nFound total of {len(pull_requests)} pull requests")
print(f"\nFound total of {len(all_pull_requests)} pull requests")

release_pr = get_prs(pull_requests, "release", "open")
release_pr = get_prs(all_pull_requests, "release", "open")

if len(release_pr) != 1:
print(f"Unable to find the exact release PR. Returned result: {release_pr}")
exit(1)

print(f"Found release PR: {release_pr[0]['title']}")

enhancement_prs = get_prs(pull_requests, "enhancement", "closed")
bug_fix_prs = get_prs(pull_requests, "bug", "closed")
release_milestone_title = release_pr[0].get("milestone", {}).get("title", None)

if not release_milestone_title:
print("Release PR does not have a milestone assigned.")
exit(1)

print(f"Using milestone number: {release_milestone_title}")

enhancement_prs = get_prs(all_pull_requests, "enhancement", "closed", release_milestone_title)
bug_fix_prs = get_prs(all_pull_requests, "bug", "closed", release_milestone_title)

if len(enhancement_prs) == 0 and len(bug_fix_prs) == 0:
print(f"No PRs with {release_milestone_title} milestone were found")

description_content = "# Release notes\n"
description_content += f"## Features\n{get_pr_descriptions(enhancement_prs)}" if enhancement_prs else ""
description_content += f"## Bug fixes\n{get_pr_descriptions(bug_fix_prs)}" if bug_fix_prs else ""

assignees = list(set(get_prs_assignees(pull_requests, "enhancement", "closed") + get_prs_assignees(pull_requests, "bug", "closed")))
assignees = list(set(get_prs_assignees(enhancement_prs) + get_prs_assignees(bug_fix_prs)))
assignees.sort(key=str.lower)

description_content += f"### Authors:\n{', '.join(assignees)}"
Expand Down
Loading