mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-16 18:40:40 +08:00
Merge pull request #278 from sarbjitsinghgrewal/fix_bitbucket_publish_description
Fix bitbucket publish description
This commit is contained in:
10
README.md
10
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: |
|
| 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: |
|
| | 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: |
|
| | 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: |
|
| | 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: |
|
| | ⮑ 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: |
|
| | Reflect and Review | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | :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: | :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: | | | |
|
| | App / webhook | :white_check_mark: | :white_check_mark: | | | |
|
||||||
|
@ -20,7 +20,7 @@ def get_setting(key: str) -> Any:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return global_settings.get(key, None)
|
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.
|
Convert a dictionary of data into markdown format.
|
||||||
Args:
|
Args:
|
||||||
@ -49,11 +49,14 @@ def convert_to_markdown(output_data: dict) -> str:
|
|||||||
continue
|
continue
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
markdown_text += f"## {key}\n\n"
|
markdown_text += f"## {key}\n\n"
|
||||||
markdown_text += convert_to_markdown(value)
|
markdown_text += convert_to_markdown(value, gfm_supported)
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
emoji = emojis.get(key, "")
|
emoji = emojis.get(key, "")
|
||||||
if key.lower() == 'code feedback':
|
if key.lower() == 'code feedback':
|
||||||
markdown_text += f"\n\n- **<details><summary> { emoji } Code feedback:**</summary>\n\n"
|
if gfm_supported:
|
||||||
|
markdown_text += f"\n\n- **<details><summary> { emoji } Code feedback:**</summary>\n\n"
|
||||||
|
else:
|
||||||
|
markdown_text += f"\n\n- **{emoji} Code feedback:**\n\n"
|
||||||
else:
|
else:
|
||||||
markdown_text += f"- {emoji} **{key}:**\n\n"
|
markdown_text += f"- {emoji} **{key}:**\n\n"
|
||||||
for item in value:
|
for item in value:
|
||||||
@ -62,7 +65,10 @@ def convert_to_markdown(output_data: dict) -> str:
|
|||||||
elif item:
|
elif item:
|
||||||
markdown_text += f" - {item}\n"
|
markdown_text += f" - {item}\n"
|
||||||
if key.lower() == 'code feedback':
|
if key.lower() == 'code feedback':
|
||||||
markdown_text += "</details>\n\n"
|
if gfm_supported:
|
||||||
|
markdown_text += "</details>\n\n"
|
||||||
|
else:
|
||||||
|
markdown_text += "\n\n"
|
||||||
elif value != 'n/a':
|
elif value != 'n/a':
|
||||||
emoji = emojis.get(key, "")
|
emoji = emojis.get(key, "")
|
||||||
markdown_text += f"- {emoji} **{key}:** {value}\n"
|
markdown_text += f"- {emoji} **{key}:** {value}\n"
|
||||||
|
@ -38,7 +38,8 @@ class AzureDevopsProvider:
|
|||||||
self.set_pr(pr_url)
|
self.set_pr(pr_url)
|
||||||
|
|
||||||
def is_supported(self, capability: str) -> bool:
|
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 False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import requests
|
|||||||
from atlassian.bitbucket import Cloud
|
from atlassian.bitbucket import Cloud
|
||||||
from starlette_context import context
|
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 ..config_loader import get_settings
|
||||||
from .git_provider import FilePatchInfo, GitProvider
|
from .git_provider import FilePatchInfo, GitProvider
|
||||||
|
|
||||||
@ -35,9 +36,8 @@ class BitbucketProvider(GitProvider):
|
|||||||
self.incremental = incremental
|
self.incremental = incremental
|
||||||
if pr_url:
|
if pr_url:
|
||||||
self.set_pr(pr_url)
|
self.set_pr(pr_url)
|
||||||
self.bitbucket_comment_api_url = self.pr._BitbucketBase__data["links"][
|
self.bitbucket_comment_api_url = self.pr._BitbucketBase__data["links"]["comments"]["href"]
|
||||||
"comments"
|
self.bitbucket_pull_request_api_url = self.pr._BitbucketBase__data["links"]['self']['href']
|
||||||
]["href"]
|
|
||||||
|
|
||||||
def get_repo_settings(self):
|
def get_repo_settings(self):
|
||||||
try:
|
try:
|
||||||
@ -101,12 +101,7 @@ class BitbucketProvider(GitProvider):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def is_supported(self, capability: str) -> bool:
|
def is_supported(self, capability: str) -> bool:
|
||||||
if capability in [
|
if capability in ['get_issue_comments', 'publish_inline_comments', 'get_labels', 'gfm_markdown']:
|
||||||
"get_issue_comments",
|
|
||||||
"create_inline_comment",
|
|
||||||
"publish_inline_comments",
|
|
||||||
"get_labels",
|
|
||||||
]:
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -151,17 +146,30 @@ class BitbucketProvider(GitProvider):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(f"Failed to remove temp comments, error: {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):
|
||||||
payload = json.dumps(
|
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:
|
||||||
"content": {
|
if get_settings().config.verbosity_level >= 2:
|
||||||
"raw": comment,
|
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
||||||
},
|
subject_type = "FILE"
|
||||||
"inline": {"to": from_line, "path": 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(
|
response = requests.request(
|
||||||
"POST", self.bitbucket_comment_api_url, data=payload, headers=self.headers
|
"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]):
|
def publish_inline_comments(self, comments: list[dict]):
|
||||||
for comment in comments:
|
for comment in comments:
|
||||||
self.publish_inline_comment(
|
self.publish_inline_comment(comment['body'], comment['start_line'], comment['path'])
|
||||||
comment["body"], comment["start_line"], comment["line"], comment["path"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.pr.title
|
return self.pr.title
|
||||||
@ -238,16 +244,22 @@ class BitbucketProvider(GitProvider):
|
|||||||
|
|
||||||
def get_commit_messages(self):
|
def get_commit_messages(self):
|
||||||
return "" # not implemented yet
|
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):
|
response = requests.request("PUT", self.bitbucket_pull_request_api_url, headers=self.headers, data=payload)
|
||||||
pass
|
return response
|
||||||
|
|
||||||
|
# bitbucket does not support labels
|
||||||
|
def publish_labels(self, pr_types: list):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# bitbucket does not support labels
|
||||||
def get_labels(self):
|
def get_labels(self):
|
||||||
pass
|
pass
|
||||||
|
@ -54,11 +54,16 @@ class CodeCommitClient:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.boto_client = None
|
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):
|
def _connect_boto_client(self):
|
||||||
try:
|
try:
|
||||||
self.boto_client = boto3.client("codecommit")
|
self.boto_client = boto3.client("codecommit")
|
||||||
except Exception as e:
|
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):
|
def get_differences(self, repo_name: int, destination_commit: str, source_commit: str):
|
||||||
"""
|
"""
|
||||||
|
@ -74,6 +74,7 @@ class CodeCommitProvider(GitProvider):
|
|||||||
"create_inline_comment",
|
"create_inline_comment",
|
||||||
"publish_inline_comments",
|
"publish_inline_comments",
|
||||||
"get_labels",
|
"get_labels",
|
||||||
|
"gfm_markdown"
|
||||||
]:
|
]:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -313,7 +313,8 @@ class GerritProvider(GitProvider):
|
|||||||
# 'get_issue_comments',
|
# 'get_issue_comments',
|
||||||
'create_inline_comment',
|
'create_inline_comment',
|
||||||
'publish_inline_comments',
|
'publish_inline_comments',
|
||||||
'get_labels'
|
'get_labels',
|
||||||
|
'gfm_markdown'
|
||||||
]:
|
]:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -43,7 +43,7 @@ class GitLabProvider(GitProvider):
|
|||||||
self.incremental = incremental
|
self.incremental = incremental
|
||||||
|
|
||||||
def is_supported(self, capability: str) -> bool:
|
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 False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -56,7 +56,8 @@ class LocalGitProvider(GitProvider):
|
|||||||
raise KeyError(f'Branch: {self.target_branch_name} does not exist')
|
raise KeyError(f'Branch: {self.target_branch_name} does not exist')
|
||||||
|
|
||||||
def is_supported(self, capability: str) -> bool:
|
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 False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -55,8 +55,12 @@ webhook_secret = "<WEBHOOK SECRET>" # Optional, may be commented out.
|
|||||||
personal_access_token = ""
|
personal_access_token = ""
|
||||||
|
|
||||||
[bitbucket]
|
[bitbucket]
|
||||||
# Bitbucket personal bearer token
|
# For Bitbucket personal/repository bearer token
|
||||||
bearer_token = ""
|
bearer_token = ""
|
||||||
|
|
||||||
|
# For Bitbucket app
|
||||||
|
app_key = ""
|
||||||
|
base_url = ""
|
||||||
|
|
||||||
[litellm]
|
[litellm]
|
||||||
LITELLM_TOKEN = "" # see https://docs.litellm.ai/docs/debugging/hosted_debugging for details and instructions on how to get a token
|
LITELLM_TOKEN = "" # see https://docs.litellm.ai/docs/debugging/hosted_debugging for details and instructions on how to get a token
|
||||||
|
@ -68,12 +68,12 @@ class PRDescription:
|
|||||||
await retry_with_fallback_models(self._prepare_prediction)
|
await retry_with_fallback_models(self._prepare_prediction)
|
||||||
|
|
||||||
logging.info('Preparing answer...')
|
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:
|
if get_settings().config.publish_output:
|
||||||
logging.info('Pushing answer...')
|
logging.info('Pushing answer...')
|
||||||
if get_settings().pr_description.publish_description_as_comment:
|
if get_settings().pr_description.publish_description_as_comment:
|
||||||
self.git_provider.publish_comment(markdown_text)
|
self.git_provider.publish_comment(pr_body)
|
||||||
else:
|
else:
|
||||||
self.git_provider.publish_description(pr_title, pr_body)
|
self.git_provider.publish_description(pr_title, pr_body)
|
||||||
if self.git_provider.is_supported("get_labels"):
|
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_body: a string containing the PR body in a markdown format.
|
||||||
- pr_types: a list of strings containing the PR types.
|
- 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
|
- 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
|
# Load the AI prediction data into a dictionary
|
||||||
data = load_yaml(self.prediction.strip())
|
data = load_yaml(self.prediction.strip())
|
||||||
@ -189,8 +190,9 @@ class PRDescription:
|
|||||||
pr_body += "\n___\n"
|
pr_body += "\n___\n"
|
||||||
|
|
||||||
markdown_text = f"## Title\n\n{title}\n\n___\n{pr_body}"
|
markdown_text = f"## Title\n\n{title}\n\n___\n{pr_body}"
|
||||||
|
description = data['PR Description']
|
||||||
|
|
||||||
if get_settings().config.verbosity_level >= 2:
|
if get_settings().config.verbosity_level >= 2:
|
||||||
logging.info(f"title:\n{title}\n{pr_body}")
|
logging.info(f"title:\n{title}\n{pr_body}")
|
||||||
|
|
||||||
return title, pr_body, pr_types, markdown_text
|
return title, pr_body, pr_types, markdown_text, description
|
@ -214,7 +214,7 @@ class PRReviewer:
|
|||||||
"⏮️ Review for commits since previous PR-Agent review": f"Starting from commit {last_commit_url}"}})
|
"⏮️ Review for commits since previous PR-Agent review": f"Starting from commit {last_commit_url}"}})
|
||||||
data.move_to_end('Incremental PR Review', last=False)
|
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()
|
user = self.git_provider.get_user_id()
|
||||||
|
|
||||||
# Add help text if not in CLI mode
|
# 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)
|
self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file)
|
||||||
|
|
||||||
if comments:
|
if comments:
|
||||||
self.git_provider.publish_inline_comments(comments)
|
self.git_provider.publish_inline_comments(comments)
|
||||||
|
|
||||||
def _get_user_answers(self) -> Tuple[str, str]:
|
def _get_user_answers(self) -> Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
|
@ -46,7 +46,7 @@ class PRUpdateChangelog:
|
|||||||
get_settings().pr_update_changelog_prompt.user)
|
get_settings().pr_update_changelog_prompt.user)
|
||||||
|
|
||||||
async def run(self):
|
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...')
|
logging.info('Updating the changelog...')
|
||||||
if get_settings().config.publish_output:
|
if get_settings().config.publish_output:
|
||||||
|
Reference in New Issue
Block a user