diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 2f3ec2c2b..122b0db3a 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -89,6 +89,12 @@ def get_user_id(self): def get_issue_comments(self): raise NotImplementedError("Bitbucket provider does not support issue comments yet") + def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: + return True + + def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: + return True + @staticmethod def _parse_pr_url(pr_url: str) -> Tuple[str, int]: parsed_url = urlparse(pr_url) diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index 677c2eb1d..8e161252d 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -3,6 +3,7 @@ # enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED) from enum import Enum +from typing import Optional class EDIT_TYPE(Enum): @@ -88,6 +89,13 @@ def get_pr_description(self): def get_issue_comments(self): pass + @abstractmethod + def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: + pass + + @abstractmethod + def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: + pass def get_main_pr_language(languages, files) -> str: """ diff --git a/pr_agent/git_providers/github_provider.py b/pr_agent/git_providers/github_provider.py index f3018e866..bc5cc6a75 100644 --- a/pr_agent/git_providers/github_provider.py +++ b/pr_agent/git_providers/github_provider.py @@ -2,10 +2,10 @@ import hashlib from datetime import datetime -from typing import Optional, Tuple +from typing import Optional, Tuple, Any from urllib.parse import urlparse -from github import AppAuthentication, Auth, Github, GithubException +from github import AppAuthentication, Auth, Github, GithubException, Reaction from retry import retry from starlette_context import context @@ -263,6 +263,23 @@ def get_repo_settings(self): except Exception: return "" + def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: + try: + reaction = self.pr.get_issue_comment(issue_comment_id).create_reaction("eyes") + return reaction.id + except Exception as e: + logging.exception(f"Failed to add eyes reaction, error: {e}") + return None + + def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: + try: + self.pr.get_issue_comment(issue_comment_id).delete_reaction(reaction_id) + return True + except Exception as e: + logging.exception(f"Failed to remove eyes reaction, error: {e}") + return False + + @staticmethod def _parse_pr_url(pr_url: str) -> Tuple[str, int]: parsed_url = urlparse(pr_url) diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 14d1d8835..a4d2d1274 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -287,6 +287,12 @@ def get_repo_settings(self): except Exception: return "" + def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: + return True + + def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: + return True + def _parse_merge_request_url(self, merge_request_url: str) -> Tuple[str, int]: parsed_url = urlparse(merge_request_url) diff --git a/pr_agent/servers/github_action_runner.py b/pr_agent/servers/github_action_runner.py index 9846e199d..52e3b1384 100644 --- a/pr_agent/servers/github_action_runner.py +++ b/pr_agent/servers/github_action_runner.py @@ -4,6 +4,7 @@ from pr_agent.agent.pr_agent import PRAgent from pr_agent.config_loader import get_settings +from pr_agent.git_providers import get_git_provider from pr_agent.tools.pr_reviewer import PRReviewer @@ -61,6 +62,9 @@ async def run_action(): pr_url = event_payload.get("issue", {}).get("pull_request", {}).get("url") if pr_url: body = comment_body.strip().lower() + comment_id = event_payload.get("comment", {}).get("id") + provider = get_git_provider()(pr_url=pr_url) + provider.add_eyes_reaction(comment_id) await PRAgent().handle_request(pr_url, body) diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 263f5ba55..1446b7e59 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -11,6 +11,7 @@ from pr_agent.agent.pr_agent import PRAgent from pr_agent.config_loader import get_settings, global_settings +from pr_agent.git_providers import get_git_provider from pr_agent.servers.utils import verify_signature logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) @@ -80,8 +81,12 @@ async def handle_request(body: Dict[str, Any]): return {} pull_request = body["issue"]["pull_request"] api_url = pull_request.get("url") + comment_id = body.get("comment", {}).get("id") + provider = get_git_provider()(pr_url=api_url) + provider.add_eyes_reaction(comment_id) await agent.handle_request(api_url, comment_body) + elif action == "opened" or 'reopened' in action: pull_request = body.get("pull_request") if not pull_request: diff --git a/pr_agent/servers/github_polling.py b/pr_agent/servers/github_polling.py index 18f71dd7d..773b34f37 100644 --- a/pr_agent/servers/github_polling.py +++ b/pr_agent/servers/github_polling.py @@ -98,7 +98,9 @@ async def polling_loop(): if user_tag not in comment_body: continue rest_of_comment = comment_body.split(user_tag)[1].strip() - + comment_id = comment['id'] + git_provider.set_pr(pr_url) + git_provider.add_eyes_reaction(comment_id) success = await agent.handle_request(pr_url, rest_of_comment) if not success: git_provider.set_pr(pr_url)