From 18df4aed6b1a1458c8b1975984ec4aa8e292b9b3 Mon Sep 17 00:00:00 2001 From: blindndangerous <20344049+blindndangerous@users.noreply.github.com> Date: Mon, 16 Feb 2026 03:20:46 -0700 Subject: [PATCH 1/2] Include PR review comments in pull request dialog --- GUI/pullrequests.py | 3 ++- github_api.py | 42 ++++++++++++++++++++++++++++++++++++++++-- models/issue.py | 6 ++++-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/GUI/pullrequests.py b/GUI/pullrequests.py index 0a89775..11a67bf 100644 --- a/GUI/pullrequests.py +++ b/GUI/pullrequests.py @@ -447,7 +447,8 @@ def update_comments(self, comments): for comment in comments: time_str = comment.created_at.strftime("%Y-%m-%d %H:%M") if comment.created_at else "Unknown" preview = comment.body[:50].replace("\n", " ") + "..." if len(comment.body) > 50 else comment.body.replace("\n", " ") - self.comments_list.Append(f"{comment.user.login} ({time_str}): {preview}") + source = "[Review] " if comment.kind == "review" else "" + self.comments_list.Append(f"{comment.user.login} ({time_str}): {source}{preview}") def on_comment_select(self, event): """Show selected comment content.""" diff --git a/github_api.py b/github_api.py index eb3cba1..6704bb4 100644 --- a/github_api.py +++ b/github_api.py @@ -737,9 +737,47 @@ def close_pull_request(self, owner: str, repo: str, number: int) -> bool: result = self.update_pull_request(owner, repo, number, state="closed") return result is not None + def get_pr_review_comments(self, owner: str, repo: str, number: int, per_page: int = 100) -> list[Comment]: + """Get review comments on a pull request.""" + comments = [] + page = 1 + + while True: + response = self._session.get( + f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{number}/comments", + params={ + "per_page": per_page, + "page": page + } + ) + + if response.status_code != 200: + break + + data = response.json() + if not data: + break + + for item in data: + comments.append(Comment.from_github_api(item, kind="review")) + + if len(data) < per_page: + break + + page += 1 + + return comments + def get_pr_comments(self, owner: str, repo: str, number: int, per_page: int = 100) -> list[Comment]: - """Get comments on a pull request (issue comments, not review comments).""" - return self.get_issue_comments(owner, repo, number, per_page) + """Get all comments on a pull request, including review comments.""" + issue_comments = self.get_issue_comments(owner, repo, number, per_page) + review_comments = self.get_pr_review_comments(owner, repo, number, per_page) + comments = issue_comments + review_comments + + # Keep stable ordering by creation time so the dialog mirrors issue behavior. + comments.sort(key=lambda c: c.created_at.timestamp() if c.created_at else 0) + + return comments def create_pr_comment(self, owner: str, repo: str, number: int, body: str) -> Comment | None: """Create a comment on a pull request.""" diff --git a/models/issue.py b/models/issue.py index f6ad7cd..463eaef 100644 --- a/models/issue.py +++ b/models/issue.py @@ -48,9 +48,10 @@ class Comment: created_at: Optional[datetime] updated_at: Optional[datetime] html_url: str = "" + kind: str = "issue" @classmethod - def from_github_api(cls, data: dict) -> 'Comment': + def from_github_api(cls, data: dict, kind: str = "issue") -> 'Comment': created_at = None if data.get('created_at'): try: @@ -71,7 +72,8 @@ def from_github_api(cls, data: dict) -> 'Comment': user=User.from_github_api(data.get('user')), created_at=created_at, updated_at=updated_at, - html_url=data.get('html_url', '') + html_url=data.get('html_url', ''), + kind=kind ) From 6f627e9e2a7dea2d54c108f6afaf0d6be7c3dcfb Mon Sep 17 00:00:00 2001 From: blindndangerous <20344049+blindndangerous@users.noreply.github.com> Date: Mon, 16 Feb 2026 03:36:08 -0700 Subject: [PATCH 2/2] Improve feed handling for review requests and review threads --- GUI/main.py | 8 ++--- models/event.py | 80 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/GUI/main.py b/GUI/main.py index 8ffb46c..ab07e75 100644 --- a/GUI/main.py +++ b/GUI/main.py @@ -432,7 +432,7 @@ def on_open_feed_event(self, event): self._open_feed_issue(owner, repo_name, number) return - elif feed_event.type in ("PullRequestEvent", "PullRequestReviewEvent", "PullRequestReviewCommentEvent"): + elif feed_event.type in ("PullRequestEvent", "PullRequestReviewEvent", "PullRequestReviewCommentEvent", "PullRequestReviewThreadEvent"): pr = payload.get("pull_request", {}) number = pr.get("number") if number: @@ -453,7 +453,7 @@ def on_open_feed_event(self, event): self._open_feed_discussion(owner, repo_name, number) return - elif feed_event.type == "PushEvent": + elif feed_event.type in ("PushEvent", "CommitCommentEvent"): self._open_feed_commits(owner, repo_name) return @@ -461,7 +461,7 @@ def on_open_feed_event(self, event): self._open_feed_releases(owner, repo_name) return - elif feed_event.type in ("WatchEvent", "ForkEvent", "CreateEvent", "PublicEvent"): + elif feed_event.type in ("WatchEvent", "ForkEvent", "CreateEvent", "PublicEvent", "GollumEvent", "MemberEvent"): # For repo-level events, show the repo dialog self._open_feed_repo_direct(owner, repo_name) return @@ -867,7 +867,7 @@ def _render_feed_list(self): def _extract_feed_pr_key(self, event): """Extract a normalized key for PR-related feed events.""" - if event.type not in ("PullRequestEvent", "PullRequestReviewEvent", "PullRequestReviewCommentEvent"): + if event.type not in ("PullRequestEvent", "PullRequestReviewEvent", "PullRequestReviewCommentEvent", "PullRequestReviewThreadEvent"): return None pr = event.payload.get("pull_request", {}) or {} diff --git a/models/event.py b/models/event.py index f27ea94..f625335 100644 --- a/models/event.py +++ b/models/event.py @@ -63,6 +63,7 @@ class Event: "PublicEvent": "made public", "PullRequestEvent": "pull request", "PullRequestReviewEvent": "reviewed PR", + "PullRequestReviewThreadEvent": "updated PR review thread", "PullRequestReviewCommentEvent": "commented on PR review", "PushEvent": "pushed", "ReleaseEvent": "released", @@ -209,22 +210,43 @@ def get_action_description(self) -> str: pr = payload.get("pull_request", {}) number = pr.get("number", "") title = pr.get("title", "")[:50] + title_suffix = f": {title}" if title else "" if action == "opened": - if title: - return f"opened PR #{number}: {title}" - return f"opened PR #{number}" + return f"opened PR #{number}{title_suffix}" + elif action == "reopened": + return f"reopened PR #{number}{title_suffix}" elif action == "closed": merged = pr.get("merged", False) if merged: - if title: - return f"merged PR #{number}: {title}" - return f"merged PR #{number}" - if title: - return f"closed PR #{number}: {title}" - return f"closed PR #{number}" - if title: - return f"{action} PR #{number}: {title}" - return f"{action} PR #{number}" + return f"merged PR #{number}{title_suffix}" + return f"closed PR #{number}{title_suffix}" + elif action == "review_requested": + reviewer = payload.get("requested_reviewer", {}) or {} + team = payload.get("requested_team", {}) or {} + reviewer_name = reviewer.get("login") + team_name = team.get("name") + if reviewer_name: + return f"requested review from {reviewer_name} on PR #{number}{title_suffix}" + if team_name: + return f"requested review from team {team_name} on PR #{number}{title_suffix}" + return f"requested review on PR #{number}{title_suffix}" + elif action == "review_request_removed": + reviewer = payload.get("requested_reviewer", {}) or {} + team = payload.get("requested_team", {}) or {} + reviewer_name = reviewer.get("login") + team_name = team.get("name") + if reviewer_name: + return f"removed review request for {reviewer_name} on PR #{number}{title_suffix}" + if team_name: + return f"removed review request for team {team_name} on PR #{number}{title_suffix}" + return f"removed review request on PR #{number}{title_suffix}" + elif action == "ready_for_review": + return f"marked PR #{number} ready for review{title_suffix}" + elif action == "converted_to_draft": + return f"converted PR #{number} to draft{title_suffix}" + elif action == "synchronize": + return f"updated PR #{number} with new commits{title_suffix}" + return f"{action} PR #{number}{title_suffix}" elif self.type == "PullRequestReviewEvent": action = payload.get("action", "") @@ -253,6 +275,18 @@ def get_action_description(self) -> str: return f"commented on PR #{number}: {title}" return f"commented on PR #{number}" + elif self.type == "PullRequestReviewThreadEvent": + action = payload.get("action", "") + pr = payload.get("pull_request", {}) + number = pr.get("number", "") + title = pr.get("title", "")[:50] + title_suffix = f": {title}" if title else "" + if action == "resolved": + return f"resolved review thread on PR #{number}{title_suffix}" + elif action == "unresolved": + return f"unresolved review thread on PR #{number}{title_suffix}" + return f"{action} review thread on PR #{number}{title_suffix}" + elif self.type == "ReleaseEvent": action = payload.get("action", "") release = payload.get("release", {}) @@ -344,7 +378,27 @@ def get_web_url(self) -> str: if number: return f"{base_url}/pull/{number}" - elif self.type == "PullRequestReviewEvent" or self.type == "PullRequestReviewCommentEvent": + elif self.type == "PullRequestReviewCommentEvent": + comment = self.payload.get("comment", {}) + html_url = comment.get("html_url") + if html_url: + return html_url + pr = self.payload.get("pull_request", {}) + number = pr.get("number") + if number: + return f"{base_url}/pull/{number}" + + elif self.type == "PullRequestReviewThreadEvent": + thread = self.payload.get("thread", {}) + html_url = thread.get("html_url") + if html_url: + return html_url + pr = self.payload.get("pull_request", {}) + number = pr.get("number") + if number: + return f"{base_url}/pull/{number}" + + elif self.type == "PullRequestReviewEvent": pr = self.payload.get("pull_request", {}) number = pr.get("number") if number: