diff --git a/pr_agent/git_providers/azuredevops_provider.py b/pr_agent/git_providers/azuredevops_provider.py index 6c45019f..309400d8 100644 --- a/pr_agent/git_providers/azuredevops_provider.py +++ b/pr_agent/git_providers/azuredevops_provider.py @@ -130,18 +130,6 @@ class AzureDevopsProvider(GitProvider): def get_pr_description_full(self) -> str: return self.pr.description - def delete_comment(self, comment): - try: - self.azure_devops_client.delete_comment( - repository_id=self.repo_slug, - pull_request_id=self.pr_num, - thread_id=comment.thread_id, - comment_id=comment.id, - project=self.workspace_slug, - ) - except Exception as e: - get_logger().exception(f"Failed to delete comment, error: {e}") - def edit_comment(self, comment, body: str): try: self.azure_devops_client.update_comment( diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index ab0b5715..4cf4f25b 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -208,9 +208,6 @@ class GitProvider(ABC): def get_comment_url(self, comment) -> str: return "" - def delete_comment(self, comment): - comment.delete() - #### labels operations #### @abstractmethod def publish_labels(self, labels): diff --git a/pr_agent/git_providers/github_provider.py b/pr_agent/git_providers/github_provider.py index 51e8d6b6..01f8aff1 100644 --- a/pr_agent/git_providers/github_provider.py +++ b/pr_agent/git_providers/github_provider.py @@ -452,8 +452,8 @@ class GithubProvider(GitProvider): def edit_comment_from_comment_id(self, comment_id: int, body: str): try: - body = self.limit_output_characters(body, self.max_comment_chars) # self.pr.get_issue_comment(comment_id).edit(body) + body = self.limit_output_characters(body, self.max_comment_chars) headers, data_patch = self.pr._requester.requestJsonAndCheck( "PATCH", f"{self.base_url}/repos/{self.repo}/issues/comments/{comment_id}", input={"body": body} @@ -463,8 +463,8 @@ class GithubProvider(GitProvider): def reply_to_comment_from_comment_id(self, comment_id: int, body: str): try: - body = self.limit_output_characters(body, self.max_comment_chars) # self.pr.get_issue_comment(comment_id).edit(body) + body = self.limit_output_characters(body, self.max_comment_chars) headers, data_patch = self.pr._requester.requestJsonAndCheck( "POST", f"{self.base_url}/repos/{self.repo}/pulls/{self.pr_num}/comments/{comment_id}/replies", input={"body": body} @@ -490,6 +490,7 @@ class GithubProvider(GitProvider): ) for comment in file_comments: comment['commit_id'] = self.last_commit_id.sha + comment['body'] = self.limit_output_characters(comment['body'], self.max_comment_chars) found = False for existing_comment in existing_comments: @@ -592,7 +593,7 @@ class GithubProvider(GitProvider): ) return data_patch.get("id", None) except Exception as e: - get_logger().exception(f"Failed to add eyes reaction, error: {e}") + get_logger().warning(f"Failed to add eyes reaction, error: {e}") return None def remove_reaction(self, issue_comment_id: int, reaction_id: str) -> bool: @@ -829,8 +830,11 @@ class GithubProvider(GitProvider): """ line_start = component_range.line_start + 1 line_end = component_range.line_end + 1 + # link = (f"https://github.com/{self.repo}/blob/{self.last_commit_id.sha}/{filepath}/" + # f"#L{line_start}-L{line_end}") link = (f"{self.base_url_html}/{self.repo}/blob/{self.last_commit_id.sha}/{filepath}/" f"#L{line_start}-L{line_end}") + return link def get_pr_id(self): diff --git a/pr_agent/servers/github_polling.py b/pr_agent/servers/github_polling.py index a0e875bc..627390c0 100644 --- a/pr_agent/servers/github_polling.py +++ b/pr_agent/servers/github_polling.py @@ -1,4 +1,5 @@ import asyncio +import traceback from datetime import datetime, timezone import aiohttp @@ -15,7 +16,7 @@ NOTIFICATION_URL = "https://api.github.com/notifications" def now() -> str: """ Get the current UTC time in ISO 8601 format. - + Returns: str: The current UTC time in ISO 8601 format. """ @@ -35,6 +36,7 @@ async def polling_loop(): user_id = git_provider.get_user_id() agent = PRAgent() get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False) + get_settings().set("pr_description.publish_description_as_comment", True) try: deployment_type = get_settings().github.deployment_type @@ -92,7 +94,8 @@ async def polling_loop(): comment_body = comment['body'] if 'body' in comment else '' commenter_github_user = comment['user']['login'] \ if 'user' in comment else '' - get_logger().info(f"Commenter: {commenter_github_user}\nComment: {comment_body}") + get_logger().info(f"Polling, pr_url: {pr_url}", + artifact={"comment": comment_body}) user_tag = "@" + user_id if user_tag not in comment_body: continue @@ -100,7 +103,8 @@ async def polling_loop(): comment_id = comment['id'] git_provider.set_pr(pr_url) success = await agent.handle_request(pr_url, rest_of_comment, - notify=lambda: git_provider.add_eyes_reaction(comment_id)) # noqa E501 + notify=lambda: git_provider.add_eyes_reaction( + comment_id)) # noqa E501 if not success: git_provider.set_pr(pr_url) @@ -108,7 +112,8 @@ async def polling_loop(): print(f"Failed to fetch notifications. Status code: {response.status}") except Exception as e: - get_logger().error(f"Exception during processing of a notification: {e}") + get_logger().error(f"Polling exception during processing of a notification: {e}", + artifact={"traceback": traceback.format_exc()}) if __name__ == '__main__': diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index 23a7cdf7..ca7b4d88 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -52,6 +52,7 @@ class PRCodeSuggestions: self.ai_handler.main_pr_language = self.main_language self.patches_diff = None self.prediction = None + self.pr_url = pr_url self.cli_mode = cli_mode self.vars = { "title": self.git_provider.pr.title, @@ -81,6 +82,10 @@ class PRCodeSuggestions: async def run(self): try: + if not self.git_provider.get_files(): + get_logger().info(f"PR has no files: {self.pr_url}, skipping code suggestions") + return None + get_logger().info('Generating code suggestions for PR...') relevant_configs = {'pr_code_suggestions': dict(get_settings().pr_code_suggestions), 'config': dict(get_settings().config)} @@ -159,6 +164,8 @@ class PRCodeSuggestions: self.push_inline_code_suggestions(data) if self.progress_response: self.progress_response.delete() + else: + get_logger().info('Code suggestions generated for PR, but not published since publish_output is False.') except Exception as e: get_logger().error(f"Failed to generate code suggestions for PR, error: {e}") if self.progress_response: @@ -177,6 +184,7 @@ class PRCodeSuggestions: final_update_message=True, max_previous_comments=4, progress_response=None): + if isinstance(self.git_provider, AzureDevopsProvider): # get_latest_commit_url is not supported yet if progress_response: self.git_provider.edit_comment(progress_response, pr_comment) @@ -256,7 +264,7 @@ class PRCodeSuggestions: get_logger().info(f"Persistent mode - updating comment {comment_url} to latest {name} message") if progress_response: # publish to 'progress_response' comment, because it refreshes immediately self.git_provider.edit_comment(progress_response, pr_comment_updated) - self.git_provider.delete_comment(comment) + self.git_provider.remove_comment(comment) else: self.git_provider.edit_comment(comment, pr_comment_updated) return @@ -361,12 +369,14 @@ class PRCodeSuggestions: one_sentence_summary_list = [] for i, suggestion in enumerate(data['code_suggestions']): try: - needed_keys = ['one_sentence_summary', 'label', 'relevant_file', 'relevant_lines_start', 'relevant_lines_end'] + needed_keys = ['one_sentence_summary', 'label', 'relevant_file', 'relevant_lines_start', + 'relevant_lines_end'] is_valid_keys = True for key in needed_keys: if key not in suggestion: is_valid_keys = False - get_logger().debug(f"Skipping suggestion {i + 1}, because it does not contain '{key}':\n'{suggestion}") + get_logger().debug( + f"Skipping suggestion {i + 1}, because it does not contain '{key}':\n'{suggestion}") break if not is_valid_keys: continue @@ -529,7 +539,7 @@ class PRCodeSuggestions: get_logger().error(f"Error getting PR diff for suggestion {i} in call {j}, error: {e}") self.data = data else: - get_logger().error(f"Error getting PR diff") + get_logger().warning(f"Empty PR diff list") self.data = data = None return data diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 28ff6abc..2bcbd89e 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -508,7 +508,8 @@ extra_file_yaml = def _prepare_file_labels(self): file_label_dict = {} - if not self.data or 'pr_files' not in self.data: + if (not self.data or not isinstance(self.data, dict) or + 'pr_files' not in self.data or not self.data['pr_files']): return file_label_dict for file in self.data['pr_files']: try: diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index b7c39fac..9f34c113 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -96,6 +96,10 @@ class PRReviewer: async def run(self) -> None: try: + if not self.git_provider.get_files(): + get_logger().info(f"PR has no files: {self.pr_url}, skipping review") + return None + if self.incremental.is_incremental and not self._can_run_incremental_review(): return None @@ -158,7 +162,7 @@ class PRReviewer: get_logger().debug(f"PR diff", diff=self.patches_diff) self.prediction = await self._get_prediction(model) else: - get_logger().error(f"Error getting PR diff") + get_logger().warning(f"Empty diff for PR: {self.pr_url}") self.prediction = None async def _get_prediction(self, model: str) -> str: