mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2026-02-20 17:28:42 +01:00
feat(pr-sync): Enhance issue management on PR merge and close events
This commit is contained in:
101
.github/scripts/pr_issue_sync/pr_issue_sync.py
vendored
101
.github/scripts/pr_issue_sync/pr_issue_sync.py
vendored
@@ -3,7 +3,7 @@ import os
|
||||
from typing import Literal
|
||||
|
||||
import requests
|
||||
from github import Github
|
||||
from github import Auth, Github
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ class ProjectItemHandler:
|
||||
|
||||
def __init__(self, gh_config: GHConfig):
|
||||
self.gh_config = gh_config
|
||||
self.gh = Github(gh_config.token)
|
||||
auth = Auth.Token(gh_config.token)
|
||||
self.gh = Github(auth=auth)
|
||||
self.repo = self.gh.get_repo(f"{gh_config.organization}/{gh_config.repository}")
|
||||
self.project_node_id = self.get_project_node_id()
|
||||
|
||||
@@ -287,6 +288,89 @@ class ProjectItemHandler:
|
||||
edges = resp["data"]["repository"]["pullRequest"]["closingIssuesReferences"]["edges"]
|
||||
return [edge["node"] for edge in edges if edge.get("node")]
|
||||
|
||||
def sync_issue_status_with_pr(self, pr_number: int):
|
||||
"""
|
||||
Sync the status of linked issues with the state of the pull request.
|
||||
If the PR is merged, close linked issues and set status to "Done".
|
||||
If the PR is closed without merging, set status to "Selected for Development" and remove assignees.
|
||||
If the PR is open, set status to "In Development" (if draft) or "Ready For Review" (if not draft) and assign to PR assignees.
|
||||
|
||||
|
||||
Args:
|
||||
pr_number (int): The pull request number.
|
||||
"""
|
||||
# Get PR info
|
||||
pr = self.repo.get_pull(pr_number)
|
||||
|
||||
# Get the linked issues of the pull request
|
||||
linked_issues = self.get_pull_request_linked_issues(pr_number=pr_number)
|
||||
print(f"Linked issues: {linked_issues}")
|
||||
|
||||
# Get PR assignees, or use PR author if no assignees
|
||||
pr_assignees = [assignee.login for assignee in pr.assignees]
|
||||
if not pr_assignees:
|
||||
pr_assignees = [pr.user.login]
|
||||
print(f"No assignees on PR, using PR author: {pr_assignees}")
|
||||
else:
|
||||
print(f"PR assignees: {pr_assignees}")
|
||||
|
||||
# Fetch all GitHub issue objects upfront
|
||||
gh_issues = {
|
||||
issue["number"]: self.repo.get_issue(issue["number"]) for issue in linked_issues
|
||||
}
|
||||
|
||||
# If the PR is merged, close all linked issues and set their status to "Done"
|
||||
# GitHub only auto-closes issues when merging to the default branch,
|
||||
# so we explicitly close them for all branches
|
||||
if pr.merged:
|
||||
print("PR is merged. Closing linked issues and setting status to 'Done'.")
|
||||
for issue in linked_issues:
|
||||
gh_issue = gh_issues[issue["number"]]
|
||||
# Close the issue if it's still open
|
||||
if gh_issue.state == "open":
|
||||
gh_issue.edit(state="closed")
|
||||
print(f"Closed issue #{issue['number']}")
|
||||
else:
|
||||
print(f"Issue #{issue['number']} already closed")
|
||||
# Set status to "Done"
|
||||
self.set_issue_status(issue_number=issue["number"], status="Done")
|
||||
print(f"Set issue #{issue['number']} status to 'Done'")
|
||||
elif pr.state == "closed":
|
||||
# PR was closed without merging - move linked issues back to "Selected for Development" and remove assignees
|
||||
print(
|
||||
"PR closed without merging. Setting linked issues status to 'Selected for Development' and removing assignees."
|
||||
)
|
||||
for issue in linked_issues:
|
||||
gh_issue = gh_issues[issue["number"]]
|
||||
# Remove all assignees
|
||||
if gh_issue.assignees:
|
||||
try:
|
||||
gh_issue.remove_from_assignees(*gh_issue.assignees)
|
||||
print(f"Removed assignees from issue #{issue['number']}")
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Warning: Could not remove assignees from issue #{issue['number']}: {e}"
|
||||
)
|
||||
self.set_issue_status(
|
||||
issue_number=issue["number"], status="Selected for Development"
|
||||
)
|
||||
print(f"Set issue #{issue['number']} status to 'Selected for Development'")
|
||||
else:
|
||||
# For open PRs, set the appropriate status and assign to PR assignees
|
||||
target_status = "In Development" if pr.draft else "Ready For Review"
|
||||
print(f"Target status: {target_status}")
|
||||
for issue in linked_issues:
|
||||
gh_issue = gh_issues[issue["number"]]
|
||||
# Assign issues to PR assignees if there are any
|
||||
current_assignees = [assignee.login for assignee in gh_issue.assignees]
|
||||
if set(current_assignees) != set(pr_assignees):
|
||||
try:
|
||||
gh_issue.edit(assignees=pr_assignees)
|
||||
print(f"Assigned issue #{issue['number']} to {pr_assignees}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not assign issue #{issue['number']}: {e}")
|
||||
self.set_issue_status(issue_number=issue["number"], status=target_status)
|
||||
|
||||
|
||||
def main():
|
||||
# GitHub settings
|
||||
@@ -324,18 +408,7 @@ def main():
|
||||
headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"},
|
||||
)
|
||||
project_item_handler = ProjectItemHandler(gh_config=gh_config)
|
||||
|
||||
# Get PR info
|
||||
pr = project_item_handler.repo.get_pull(pr_number)
|
||||
|
||||
# Get the linked issues of the pull request
|
||||
linked_issues = project_item_handler.get_pull_request_linked_issues(pr_number=pr_number)
|
||||
print(f"Linked issues: {linked_issues}")
|
||||
|
||||
target_status = "In Development" if pr.draft else "Ready For Review"
|
||||
print(f"Target status: {target_status}")
|
||||
for issue in linked_issues:
|
||||
project_item_handler.set_issue_status(issue_number=issue["number"], status=target_status)
|
||||
project_item_handler.sync_issue_status_with_pr(pr_number=pr_number)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
25
.github/workflows/sync-issues-pr.yml
vendored
25
.github/workflows/sync-issues-pr.yml
vendored
@@ -2,7 +2,18 @@ name: Sync PR to Project
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, ready_for_review, converted_to_draft, reopened, synchronize]
|
||||
types:
|
||||
[
|
||||
opened,
|
||||
assigned,
|
||||
unassigned,
|
||||
edited,
|
||||
ready_for_review,
|
||||
converted_to_draft,
|
||||
reopened,
|
||||
synchronize,
|
||||
closed,
|
||||
]
|
||||
|
||||
jobs:
|
||||
sync-project:
|
||||
@@ -14,11 +25,11 @@ jobs:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PROJECT_NUMBER: 3 # BEC Project
|
||||
ORG: 'bec-project'
|
||||
REPO: 'ophyd_devices'
|
||||
TOKEN: ${{ secrets.ADD_ISSUE_TO_PROJECT }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PROJECT_NUMBER: 3 # BEC Project
|
||||
ORG: "bec-project"
|
||||
REPO: "ophyd_devices"
|
||||
TOKEN: ${{ secrets.ADD_ISSUE_TO_PROJECT }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
steps:
|
||||
- name: Set up python environment
|
||||
@@ -37,4 +48,4 @@ jobs:
|
||||
pip install -r ./.github/scripts/pr_issue_sync/requirements.txt
|
||||
- name: Sync PR to Project
|
||||
run: |
|
||||
python ./.github/scripts/pr_issue_sync/pr_issue_sync.py
|
||||
python ./.github/scripts/pr_issue_sync/pr_issue_sync.py
|
||||
|
||||
Reference in New Issue
Block a user