diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 2f3ec2c2..122b0db3 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -89,6 +89,12 @@ class BitbucketProvider: 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 677c2eb1..8e161252 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -3,6 +3,7 @@ from dataclasses import dataclass # enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED) from enum import Enum +from typing import Optional class EDIT_TYPE(Enum): @@ -88,6 +89,13 @@ class GitProvider(ABC): 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 f3018e86..bc5cc6a7 100644 --- a/pr_agent/git_providers/github_provider.py +++ b/pr_agent/git_providers/github_provider.py @@ -2,10 +2,10 @@ import logging 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 @@ class GithubProvider(GitProvider): 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 14d1d883..a4d2d127 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -287,6 +287,12 @@ class GitLabProvider(GitProvider): 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 9846e199..5c42527c 100644 --- a/pr_agent/servers/github_action_runner.py +++ b/pr_agent/servers/github_action_runner.py @@ -4,6 +4,7 @@ import os 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,7 +62,12 @@ 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) + added_reaction = provider.add_eyes_reaction(comment_id) await PRAgent().handle_request(pr_url, body) + if added_reaction: + provider.remove_reaction(comment_id, added_reaction) if __name__ == '__main__': diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 263f5ba5..fe7fa354 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -11,6 +11,7 @@ from starlette_context.middleware import RawContextMiddleware 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,7 +81,13 @@ 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) + added_reaction = provider.add_eyes_reaction(comment_id) await agent.handle_request(api_url, comment_body) + if added_reaction: + provider.remove_reaction(comment_id, added_reaction) + elif action == "opened" or 'reopened' in action: pull_request = body.get("pull_request") diff --git a/pr_agent/servers/github_polling.py b/pr_agent/servers/github_polling.py index 18f71dd7..007b3e9d 100644 --- a/pr_agent/servers/github_polling.py +++ b/pr_agent/servers/github_polling.py @@ -98,8 +98,12 @@ 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) + added_reaction = git_provider.add_eyes_reaction(comment_id) success = await agent.handle_request(pr_url, rest_of_comment) + if added_reaction: + git_provider.remove_reaction(comment_id, added_reaction) if not success: git_provider.set_pr(pr_url) git_provider.publish_comment("### How to use PR-Agent\n" +