From 67ff50583abb6f6f943e9adddee7f007f4895de3 Mon Sep 17 00:00:00 2001 From: sarbjitgrewal Date: Thu, 24 Aug 2023 11:52:20 +0530 Subject: [PATCH 1/5] fix improve, update_changelog and review inline comment --- pr_agent/git_providers/bitbucket_provider.py | 25 ++++++++++++++++---- pr_agent/tools/pr_description.py | 2 +- pr_agent/tools/pr_reviewer.py | 5 +++- pr_agent/tools/pr_update_changelog.py | 2 +- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 97275742..b1c49b1e 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse import requests from atlassian.bitbucket import Cloud -from ..algo.pr_processing import clip_tokens +from ..algo.pr_processing import clip_tokens, find_line_number_of_relevant_line_in_file from ..config_loader import get_settings from .git_provider import FilePatchInfo @@ -87,7 +87,7 @@ class BitbucketProvider: return False def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels']: + if capability in ['get_issue_comments', 'publish_inline_comments', 'get_labels']: return False return True @@ -122,7 +122,20 @@ class BitbucketProvider: except Exception as e: logging.exception(f"Failed to remove temp comments, error: {e}") - def publish_inline_comment(self, comment: str, from_line: int, to_line: int, file: str): + # funtion to create_inline_comment + def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): + position, absolute_position = find_line_number_of_relevant_line_in_file(self.get_diff_files(), relevant_file.strip('`'), relevant_line_in_file) + if position == -1: + if get_settings().config.verbosity_level >= 2: + logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}") + subject_type = "FILE" + else: + subject_type = "LINE" + path = relevant_file.strip() + return dict(body=body, path=path, position=absolute_position) if subject_type == "LINE" else {} + + + def publish_inline_comment(self, comment: str, from_line: int, file: str): payload = json.dumps( { "content": { "raw": comment, @@ -144,7 +157,11 @@ class BitbucketProvider: def publish_inline_comments(self, comments: list[dict]): for comment in comments: - self.publish_inline_comment(comment['body'], comment['start_line'], comment['line'], comment['path']) + self.publish_inline_comment(comment['body'], comment['start_line'], comment['path']) + + def publish_bitbucket_inline_comments(self, comments: list[dict]): + for comment in comments: + self.publish_inline_comment(comment['body'],comment['position'], comment['path']) def get_title(self): return self.pr.title diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index d55dd55a..79ad19e2 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -71,7 +71,7 @@ class PRDescription: if get_settings().config.publish_output: logging.info('Pushing answer...') if get_settings().pr_description.publish_description_as_comment: - self.git_provider.publish_comment(markdown_text) + self.git_provider.publish_comment(pr_body) else: self.git_provider.publish_description(pr_title, pr_body) if self.git_provider.is_supported("get_labels"): diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index a89c27a3..a00fe360 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -266,7 +266,10 @@ class PRReviewer: self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file) if comments: - self.git_provider.publish_inline_comments(comments) + if get_settings().config.git_provider == 'bitbucket': + self.git_provider.publish_bitbucket_inline_comments(comments) + else: + self.git_provider.publish_inline_comments(comments) def _get_user_answers(self) -> Tuple[str, str]: """ diff --git a/pr_agent/tools/pr_update_changelog.py b/pr_agent/tools/pr_update_changelog.py index 1ec62709..547ce84b 100644 --- a/pr_agent/tools/pr_update_changelog.py +++ b/pr_agent/tools/pr_update_changelog.py @@ -46,7 +46,7 @@ class PRUpdateChangelog: get_settings().pr_update_changelog_prompt.user) async def run(self): - assert type(self.git_provider) == GithubProvider, "Currently only Github is supported" + # assert type(self.git_provider) == GithubProvider, "Currently only Github is supported" logging.info('Updating the changelog...') if get_settings().config.publish_output: From db6bf41051c6c5c72dd6bc5b2f8804c4a8b75ac8 Mon Sep 17 00:00:00 2001 From: sarbjitgrewal Date: Thu, 24 Aug 2023 15:56:20 +0530 Subject: [PATCH 2/5] update readme --- README.md | 8 ++++---- pr_agent/git_providers/bitbucket_provider.py | 4 ++++ pr_agent/tools/pr_description.py | 16 ++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d2c4e171..19747071 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,12 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | | GitHub | Gitlab | Bitbucket | |-------|---------------------------------------------|:------:|:------:|:---------:| | TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | +| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: -| | Auto-Description | :white_check_mark: | :white_check_mark: | | -| | Improve Code | :white_check_mark: | :white_check_mark: | | +| | Auto-Description | :white_check_mark: | :white_check_mark: | :white_check_mark: +| | Improve Code | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Reflect and Review | :white_check_mark: | | | -| | Update CHANGELOG.md | :white_check_mark: | | | +| | Update CHANGELOG.md | :white_check_mark: | | :white_check_mark: | | | | | | | USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | App / webhook | :white_check_mark: | :white_check_mark: | | diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index b1c49b1e..62b5a0c9 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -225,3 +225,7 @@ class BitbucketProvider: def get_commit_messages(self): return "" # not implemented yet + + # bitbucket does not support labels + def publish_description(self, pr_title: str, pr_body: str): + return "" diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 79ad19e2..9ed81c20 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -73,12 +73,16 @@ class PRDescription: if get_settings().pr_description.publish_description_as_comment: self.git_provider.publish_comment(pr_body) else: - self.git_provider.publish_description(pr_title, pr_body) - if self.git_provider.is_supported("get_labels"): - current_labels = self.git_provider.get_labels() - if current_labels is None: - current_labels = [] - self.git_provider.publish_labels(pr_types + current_labels) + # bitbucket does not support publishing PR labels yet + if get_settings().config.git_provider == 'bitbucket': + return + else: + self.git_provider.publish_description(pr_title, pr_body) + if self.git_provider.is_supported("get_labels"): + current_labels = self.git_provider.get_labels() + if current_labels is None: + current_labels = [] + self.git_provider.publish_labels(pr_types + current_labels) self.git_provider.remove_initial_comment() return "" From 0e42634da4ef5aeef04a6e3d673f939e41a9c5ce Mon Sep 17 00:00:00 2001 From: sarbjitgrewal Date: Fri, 25 Aug 2023 10:15:30 +0530 Subject: [PATCH 3/5] add publish_labels and get_labels functions --- pr_agent/git_providers/bitbucket_provider.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index bca6edbc..fbfdffe9 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -226,3 +226,11 @@ class BitbucketProvider(GitProvider): # bitbucket does not support labels def publish_description(self, pr_title: str, pr_body: str): return "" + + # bitbucket does not support labels + def publish_labels(self, pr_types: list): + return "" + + # bitbucket does not support labels + def get_labels(self): + return "" From 335877c4a74678075bcdec2be58b544bd94147fc Mon Sep 17 00:00:00 2001 From: sarbjitgrewal Date: Wed, 6 Sep 2023 09:26:23 +0530 Subject: [PATCH 4/5] fix publish description for bitbucket --- pr_agent/git_providers/bitbucket_provider.py | 16 +++++++++++----- pr_agent/tools/pr_description.py | 7 +++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index a4036575..e2b06feb 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -36,9 +36,8 @@ class BitbucketProvider(GitProvider): self.incremental = incremental if pr_url: self.set_pr(pr_url) - self.bitbucket_comment_api_url = self.pr._BitbucketBase__data["links"][ - "comments" - ]["href"] + self.bitbucket_comment_api_url = self.pr._BitbucketBase__data["links"]["comments"]["href"] + self.bitbucket_pull_request_api_url = self.pr._BitbucketBase__data["links"]['self']['href'] def get_repo_settings(self): try: @@ -253,8 +252,15 @@ class BitbucketProvider(GitProvider): return "" # not implemented yet # bitbucket does not support labels - def publish_description(self, pr_title: str, pr_body: str): - pass + def publish_description(self, pr_title: str, description: str): + payload = json.dumps({ + "description": description, + "title": pr_title + + }) + + response = requests.request("PUT", self.bitbucket_pull_request_api_url, headers=self.headers, data=payload) + return response # bitbucket does not support labels def publish_labels(self, pr_types: list): diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index b5074e4e..e5430f92 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -68,7 +68,7 @@ class PRDescription: await retry_with_fallback_models(self._prepare_prediction) logging.info('Preparing answer...') - pr_title, pr_body, pr_types, markdown_text = self._prepare_pr_answer() + pr_title, pr_body, pr_types, markdown_text, description = self._prepare_pr_answer() if get_settings().config.publish_output: logging.info('Pushing answer...') @@ -77,6 +77,7 @@ class PRDescription: else: # bitbucket does not support publishing PR labels yet if get_settings().config.git_provider == 'bitbucket': + self.git_provider.publish_description(pr_title, description) return else: self.git_provider.publish_description(pr_title, pr_body) @@ -147,6 +148,7 @@ class PRDescription: - pr_body: a string containing the PR body in a markdown format. - pr_types: a list of strings containing the PR types. - markdown_text: a string containing the AI prediction data in a markdown format. used for publishing a comment + - user_description: a string containing the user description """ # Load the AI prediction data into a dictionary data = load_yaml(self.prediction.strip()) @@ -193,8 +195,9 @@ class PRDescription: pr_body += "\n___\n" markdown_text = f"## Title\n\n{title}\n\n___\n{pr_body}" + description = data['PR Description'] if get_settings().config.verbosity_level >= 2: logging.info(f"title:\n{title}\n{pr_body}") - return title, pr_body, pr_types, markdown_text \ No newline at end of file + return title, pr_body, pr_types, markdown_text, description \ No newline at end of file From 115b513c9b7c3cd7268416c075094d44f9e70c28 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Sun, 10 Sep 2023 14:06:13 +0300 Subject: [PATCH 5/5] Remove 'bitbucket' explicit dependency anywhere that's not in bitbucket_provider.py --- pr_agent/algo/utils.py | 14 ++++++++++---- pr_agent/git_providers/azuredevops_provider.py | 3 ++- pr_agent/git_providers/bitbucket_provider.py | 8 +------- pr_agent/git_providers/codecommit_client.py | 7 ++++++- pr_agent/git_providers/codecommit_provider.py | 1 + pr_agent/git_providers/gerrit_provider.py | 3 ++- pr_agent/git_providers/gitlab_provider.py | 2 +- pr_agent/git_providers/local_git_provider.py | 3 ++- pr_agent/settings/.secrets_template.toml | 7 ++++++- pr_agent/tools/pr_description.py | 17 ++++++----------- pr_agent/tools/pr_reviewer.py | 5 +---- 11 files changed, 38 insertions(+), 32 deletions(-) diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 1259a46e..c874de9b 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -20,7 +20,7 @@ def get_setting(key: str) -> Any: except Exception: return global_settings.get(key, None) -def convert_to_markdown(output_data: dict) -> str: +def convert_to_markdown(output_data: dict, gfm_supported: bool) -> str: """ Convert a dictionary of data into markdown format. Args: @@ -49,11 +49,14 @@ def convert_to_markdown(output_data: dict) -> str: continue if isinstance(value, dict): markdown_text += f"## {key}\n\n" - markdown_text += convert_to_markdown(value) + markdown_text += convert_to_markdown(value, gfm_supported) elif isinstance(value, list): emoji = emojis.get(key, "") if key.lower() == 'code feedback': - markdown_text += f"\n\n- **
{ emoji } Code feedback:**\n\n" + if gfm_supported: + markdown_text += f"\n\n- **
{ emoji } Code feedback:**\n\n" + else: + markdown_text += f"\n\n- **{emoji} Code feedback:**\n\n" else: markdown_text += f"- {emoji} **{key}:**\n\n" for item in value: @@ -62,7 +65,10 @@ def convert_to_markdown(output_data: dict) -> str: elif item: markdown_text += f" - {item}\n" if key.lower() == 'code feedback': - markdown_text += "
\n\n" + if gfm_supported: + markdown_text += "
\n\n" + else: + markdown_text += "\n\n" elif value != 'n/a': emoji = emojis.get(key, "") markdown_text += f"- {emoji} **{key}:** {value}\n" diff --git a/pr_agent/git_providers/azuredevops_provider.py b/pr_agent/git_providers/azuredevops_provider.py index 71ae0947..8a7693ce 100644 --- a/pr_agent/git_providers/azuredevops_provider.py +++ b/pr_agent/git_providers/azuredevops_provider.py @@ -38,7 +38,8 @@ class AzureDevopsProvider: self.set_pr(pr_url) def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels', 'remove_initial_comment']: + if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels', + 'remove_initial_comment', 'gfm_markdown']: return False return True diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index e2b06feb..56b9f711 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -101,8 +101,7 @@ class BitbucketProvider(GitProvider): return False def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'publish_inline_comments', 'get_labels']: - + if capability in ['get_issue_comments', 'publish_inline_comments', 'get_labels', 'gfm_markdown']: return False return True @@ -180,11 +179,6 @@ class BitbucketProvider(GitProvider): for comment in comments: self.publish_inline_comment(comment['body'], comment['start_line'], comment['path']) - def publish_bitbucket_inline_comments(self, comments: list[dict]): - for comment in comments: - self.publish_inline_comment(comment['body'],comment['position'], comment['path']) - - def get_title(self): return self.pr.title diff --git a/pr_agent/git_providers/codecommit_client.py b/pr_agent/git_providers/codecommit_client.py index 1112ee22..5f18c90d 100644 --- a/pr_agent/git_providers/codecommit_client.py +++ b/pr_agent/git_providers/codecommit_client.py @@ -54,11 +54,16 @@ class CodeCommitClient: def __init__(self): self.boto_client = None + def is_supported(self, capability: str) -> bool: + if capability in ["gfm_markdown"]: + return False + return True + def _connect_boto_client(self): try: self.boto_client = boto3.client("codecommit") except Exception as e: - raise ValueError(f"Failed to connect to AWS CodeCommit: {e}") + raise ValueError(f"Failed to connect to AWS CodeCommit: {e}") from e def get_differences(self, repo_name: int, destination_commit: str, source_commit: str): """ diff --git a/pr_agent/git_providers/codecommit_provider.py b/pr_agent/git_providers/codecommit_provider.py index 1f570a1a..5361f665 100644 --- a/pr_agent/git_providers/codecommit_provider.py +++ b/pr_agent/git_providers/codecommit_provider.py @@ -74,6 +74,7 @@ class CodeCommitProvider(GitProvider): "create_inline_comment", "publish_inline_comments", "get_labels", + "gfm_markdown" ]: return False return True diff --git a/pr_agent/git_providers/gerrit_provider.py b/pr_agent/git_providers/gerrit_provider.py index 03faf2a1..8399d14e 100644 --- a/pr_agent/git_providers/gerrit_provider.py +++ b/pr_agent/git_providers/gerrit_provider.py @@ -304,7 +304,8 @@ class GerritProvider(GitProvider): # 'get_issue_comments', 'create_inline_comment', 'publish_inline_comments', - 'get_labels' + 'get_labels', + 'gfm_markdown' ]: return False return True diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 2deae177..a1d0b334 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -43,7 +43,7 @@ class GitLabProvider(GitProvider): self.incremental = incremental def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments']: + if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'gfm_markdown']: return False return True diff --git a/pr_agent/git_providers/local_git_provider.py b/pr_agent/git_providers/local_git_provider.py index e6ee1456..ac750371 100644 --- a/pr_agent/git_providers/local_git_provider.py +++ b/pr_agent/git_providers/local_git_provider.py @@ -56,7 +56,8 @@ class LocalGitProvider(GitProvider): raise KeyError(f'Branch: {self.target_branch_name} does not exist') def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels']: + if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels', + 'gfm_markdown']: return False return True diff --git a/pr_agent/settings/.secrets_template.toml b/pr_agent/settings/.secrets_template.toml index 0ac75519..0bfdc4e4 100644 --- a/pr_agent/settings/.secrets_template.toml +++ b/pr_agent/settings/.secrets_template.toml @@ -43,5 +43,10 @@ webhook_secret = "" # Optional, may be commented out. personal_access_token = "" [bitbucket] -# Bitbucket personal bearer token +# For Bitbucket personal/repository bearer token bearer_token = "" + +# For Bitbucket app +app_key = "" +base_url = "" + diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index e5430f92..f30b0165 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -75,17 +75,12 @@ class PRDescription: if get_settings().pr_description.publish_description_as_comment: self.git_provider.publish_comment(pr_body) else: - # bitbucket does not support publishing PR labels yet - if get_settings().config.git_provider == 'bitbucket': - self.git_provider.publish_description(pr_title, description) - return - else: - self.git_provider.publish_description(pr_title, pr_body) - if self.git_provider.is_supported("get_labels"): - current_labels = self.git_provider.get_labels() - if current_labels is None: - current_labels = [] - self.git_provider.publish_labels(pr_types + current_labels) + self.git_provider.publish_description(pr_title, pr_body) + if self.git_provider.is_supported("get_labels"): + current_labels = self.git_provider.get_labels() + if current_labels is None: + current_labels = [] + self.git_provider.publish_labels(pr_types + current_labels) self.git_provider.remove_initial_comment() return "" diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index a00fe360..7f790d3b 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -214,7 +214,7 @@ class PRReviewer: "⏮️ Review for commits since previous PR-Agent review": f"Starting from commit {last_commit_url}"}}) data.move_to_end('Incremental PR Review', last=False) - markdown_text = convert_to_markdown(data) + markdown_text = convert_to_markdown(data, self.git_provider.is_supported("gfm_markdown")) user = self.git_provider.get_user_id() # Add help text if not in CLI mode @@ -266,9 +266,6 @@ class PRReviewer: self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file) if comments: - if get_settings().config.git_provider == 'bitbucket': - self.git_provider.publish_bitbucket_inline_comments(comments) - else: self.git_provider.publish_inline_comments(comments) def _get_user_answers(self) -> Tuple[str, str]: