diff --git a/README.md b/README.md index ade40209..fb7a0b64 100644 --- a/README.md +++ b/README.md @@ -100,11 +100,11 @@ See the [usage guide](./Usage.md) for instructions how to run the different tool |-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:|:----------:| | TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Improve Code | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | -| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | -| | Reflect and Review | :white_check_mark: | | | | :white_check_mark: | :white_check_mark: | -| | Update CHANGELOG.md | :white_check_mark: | | | | | | +| | Auto-Description | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Improve Code | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| | Reflect and Review | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | :white_check_mark: | +| | Update CHANGELOG.md | :white_check_mark: | | :white_check_mark: | | | | | | | | | | | | | USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | App / webhook | :white_check_mark: | :white_check_mark: | | | | diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 7ac4b468..ac865471 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 0cd860fa..56b9f711 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -7,6 +7,7 @@ import requests from atlassian.bitbucket import Cloud from starlette_context import context +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, GitProvider @@ -35,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: @@ -101,12 +101,7 @@ class BitbucketProvider(GitProvider): 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', 'gfm_markdown']: return False return True @@ -151,17 +146,30 @@ class BitbucketProvider(GitProvider): 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 - ): - payload = json.dumps( - { - "content": { - "raw": comment, - }, - "inline": {"to": from_line, "path": file}, - } - ) + + # 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, + }, + "inline": { + "to": from_line, + "path": file + }, + }) response = requests.request( "POST", self.bitbucket_comment_api_url, data=payload, headers=self.headers ) @@ -169,9 +177,7 @@ class BitbucketProvider(GitProvider): 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 get_title(self): return self.pr.title @@ -238,16 +244,22 @@ class BitbucketProvider(GitProvider): def get_commit_messages(self): return "" # not implemented yet + + # bitbucket does not support labels + def publish_description(self, pr_title: str, description: str): + payload = json.dumps({ + "description": description, + "title": pr_title - def publish_description(self, pr_title: str, pr_body: str): - pass - def create_inline_comment( - self, body: str, relevant_file: str, relevant_line_in_file: str - ): - pass + }) - def publish_labels(self, labels): - pass + 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): + pass + + # bitbucket does not support labels def get_labels(self): pass 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 7f71ed6d..dd56803a 100644 --- a/pr_agent/git_providers/gerrit_provider.py +++ b/pr_agent/git_providers/gerrit_provider.py @@ -313,7 +313,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 f1971a3b..0271a2c3 100644 --- a/pr_agent/settings/.secrets_template.toml +++ b/pr_agent/settings/.secrets_template.toml @@ -55,8 +55,12 @@ 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 = "" + [litellm] -LITELLM_TOKEN = "" # see https://docs.litellm.ai/docs/debugging/hosted_debugging for details and instructions on how to get a token \ No newline at end of file +LITELLM_TOKEN = "" # see https://docs.litellm.ai/docs/debugging/hosted_debugging for details and instructions on how to get a token diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index c45917f4..f30b0165 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -68,12 +68,12 @@ 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...') 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"): @@ -143,6 +143,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()) @@ -189,8 +190,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 diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index a89c27a3..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,7 +266,7 @@ class PRReviewer: self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file) if comments: - self.git_provider.publish_inline_comments(comments) + 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: