mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-05 13:20:39 +08:00
Merge pull request #76 from zmeir/zmeir-publish_inline_comments_single_api_call
Optimization of Inline Comments Publishing
This commit is contained in:
@ -26,7 +26,7 @@ class BitbucketProvider:
|
|||||||
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 == 'get_issue_comments':
|
if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments']:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -64,6 +64,12 @@ class BitbucketProvider:
|
|||||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
|
raise NotImplementedError("Bitbucket provider does not support creating inline comments yet")
|
||||||
|
|
||||||
|
def publish_inline_comments(self, comments: list[dict]):
|
||||||
|
raise NotImplementedError("Bitbucket provider does not support publishing inline comments yet")
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.pr.title
|
return self.pr.title
|
||||||
|
|
||||||
|
@ -44,6 +44,14 @@ class GitProvider(ABC):
|
|||||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def publish_inline_comments(self, comments: list[dict]):
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def publish_code_suggestion(self, body: str, relevant_file: str,
|
def publish_code_suggestion(self, body: str, relevant_file: str,
|
||||||
relevant_lines_start: int, relevant_lines_end: int):
|
relevant_lines_start: int, relevant_lines_end: int):
|
||||||
|
@ -3,7 +3,7 @@ from datetime import datetime
|
|||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from github import AppAuthentication, Github
|
from github import AppAuthentication, Github, Auth
|
||||||
|
|
||||||
from pr_agent.config_loader import settings
|
from pr_agent.config_loader import settings
|
||||||
|
|
||||||
@ -57,6 +57,9 @@ class GithubProvider(GitProvider):
|
|||||||
self.pr.comments_list.append(response)
|
self.pr.comments_list.append(response)
|
||||||
|
|
||||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
|
self.publish_inline_comments([self.create_inline_comment(body, relevant_file, relevant_line_in_file)])
|
||||||
|
|
||||||
|
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
|
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
|
||||||
position = -1
|
position = -1
|
||||||
for file in self.diff_files:
|
for file in self.diff_files:
|
||||||
@ -75,9 +78,16 @@ class GithubProvider(GitProvider):
|
|||||||
if position == -1:
|
if position == -1:
|
||||||
if settings.config.verbosity_level >= 2:
|
if settings.config.verbosity_level >= 2:
|
||||||
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
||||||
|
subject_type = "FILE"
|
||||||
else:
|
else:
|
||||||
path = relevant_file.strip()
|
subject_type = "LINE"
|
||||||
self.pr.create_review_comment(body=body, commit_id=self.last_commit_id, path=path, position=position)
|
path = relevant_file.strip()
|
||||||
|
# placeholder for future API support (already supported in single inline comment)
|
||||||
|
# return dict(body=body, path=path, position=position, subject_type=subject_type)
|
||||||
|
return dict(body=body, path=path, position=position) if subject_type == "LINE" else {}
|
||||||
|
|
||||||
|
def publish_inline_comments(self, comments: list[dict]):
|
||||||
|
self.pr.create_review(commit=self.last_commit_id, comments=comments)
|
||||||
|
|
||||||
def publish_code_suggestion(self, body: str,
|
def publish_code_suggestion(self, body: str,
|
||||||
relevant_file: str,
|
relevant_file: str,
|
||||||
@ -218,7 +228,7 @@ class GithubProvider(GitProvider):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
"GitHub token is required when using user deployment. See: "
|
"GitHub token is required when using user deployment. See: "
|
||||||
"https://github.com/Codium-ai/pr-agent#method-2-run-from-source") from e
|
"https://github.com/Codium-ai/pr-agent#method-2-run-from-source") from e
|
||||||
return Github(token)
|
return Github(auth=Auth.Token(token))
|
||||||
|
|
||||||
def _get_repo(self):
|
def _get_repo(self):
|
||||||
return self.github_client.get_repo(self.repo)
|
return self.github_client.get_repo(self.repo)
|
||||||
|
@ -33,7 +33,7 @@ class GitLabProvider(GitProvider):
|
|||||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||||
|
|
||||||
def is_supported(self, capability: str) -> bool:
|
def is_supported(self, capability: str) -> bool:
|
||||||
if capability == 'get_issue_comments':
|
if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments']:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -102,6 +102,12 @@ class GitLabProvider(GitProvider):
|
|||||||
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||||
target_file, target_line_no)
|
target_file, target_line_no)
|
||||||
|
|
||||||
|
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||||
|
raise NotImplementedError("Gitlab provider does not support creating inline comments yet")
|
||||||
|
|
||||||
|
def create_inline_comment(self, comments: list[dict]):
|
||||||
|
raise NotImplementedError("Gitlab provider does not support publishing inline comments yet")
|
||||||
|
|
||||||
def send_inline_comment(self, body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
def send_inline_comment(self, body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||||
target_file, target_line_no):
|
target_file, target_line_no):
|
||||||
if not found:
|
if not found:
|
||||||
|
@ -51,7 +51,7 @@ class PRReviewer:
|
|||||||
async def review(self):
|
async def review(self):
|
||||||
logging.info('Reviewing PR...')
|
logging.info('Reviewing PR...')
|
||||||
if settings.config.publish_output:
|
if settings.config.publish_output:
|
||||||
self.git_provider.publish_comment("Preparing review...", is_temporary=True)
|
self.git_provider.publish_comment("Preparing review...", is_temporary=True)
|
||||||
logging.info('Getting PR diff...')
|
logging.info('Getting PR diff...')
|
||||||
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler)
|
self.patches_diff = get_pr_diff(self.git_provider, self.token_handler)
|
||||||
logging.info('Getting AI prediction...')
|
logging.info('Getting AI prediction...')
|
||||||
@ -99,7 +99,13 @@ class PRReviewer:
|
|||||||
if settings.config.git_provider == 'github' and \
|
if settings.config.git_provider == 'github' and \
|
||||||
settings.pr_reviewer.inline_code_comments and \
|
settings.pr_reviewer.inline_code_comments and \
|
||||||
'Code suggestions' in data['PR Feedback']:
|
'Code suggestions' in data['PR Feedback']:
|
||||||
del data['PR Feedback']['Code suggestions']
|
# keeping only code suggestions that can't be submitted as inline comments
|
||||||
|
data['PR Feedback']['Code suggestions'] = [
|
||||||
|
d for d in data['PR Feedback']['Code suggestions']
|
||||||
|
if any(key not in d for key in ('relevant file', 'relevant line in file', 'suggestion content'))
|
||||||
|
]
|
||||||
|
if not data['PR Feedback']['Code suggestions']:
|
||||||
|
del data['PR Feedback']['Code suggestions']
|
||||||
|
|
||||||
markdown_text = convert_to_markdown(data)
|
markdown_text = convert_to_markdown(data)
|
||||||
user = self.git_provider.get_user_id()
|
user = self.git_provider.get_user_id()
|
||||||
@ -125,16 +131,24 @@ class PRReviewer:
|
|||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
data = try_fix_json(review)
|
data = try_fix_json(review)
|
||||||
|
|
||||||
if settings.pr_reviewer.num_code_suggestions > 0:
|
comments = []
|
||||||
try:
|
for d in data['PR Feedback']['Code suggestions']:
|
||||||
for d in data['PR Feedback']['Code suggestions']:
|
relevant_file = d.get('relevant file', '').strip()
|
||||||
relevant_file = d['relevant file'].strip()
|
relevant_line_in_file = d.get('relevant line in file', '').strip()
|
||||||
relevant_line_in_file = d['relevant line in file'].strip()
|
content = d.get('suggestion content', '')
|
||||||
content = d['suggestion content']
|
if not relevant_file or not relevant_line_in_file or not content:
|
||||||
|
logging.info("Skipping inline comment with missing file/line/content")
|
||||||
|
continue
|
||||||
|
|
||||||
self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file)
|
if self.git_provider.is_supported("create_inline_comment"):
|
||||||
except KeyError:
|
comment = self.git_provider.create_inline_comment(content, relevant_file, relevant_line_in_file)
|
||||||
pass
|
if comment:
|
||||||
|
comments.append(comment)
|
||||||
|
else:
|
||||||
|
self.git_provider.publish_inline_comment(content, relevant_file, relevant_line_in_file)
|
||||||
|
|
||||||
|
if comments:
|
||||||
|
self.git_provider.publish_inline_comments(comments)
|
||||||
|
|
||||||
def _get_user_answers(self):
|
def _get_user_answers(self):
|
||||||
answer_str = question_str = ""
|
answer_str = question_str = ""
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
dynaconf==3.1.12
|
dynaconf==3.1.12
|
||||||
fastapi==0.99.0
|
fastapi==0.99.0
|
||||||
PyGithub==1.58.2
|
PyGithub==1.59.*
|
||||||
retry==0.9.2
|
retry==0.9.2
|
||||||
openai==0.27.8
|
openai==0.27.8
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
|
Reference in New Issue
Block a user