Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement of Persistent Comments in PR Review #451

Merged
merged 5 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@ The `review` tool can also be triggered automatically every time a new PR is ope

Under the section 'pr_reviewer', the [configuration file](./../pr_agent/settings/configuration.toml#L16) contains options to customize the 'review' tool:

#### enable\\disable features
- `require_focused_review`: if set to true, the tool will add a section - 'is the PR a focused one'. Default is false.
- `require_score_review`: if set to true, the tool will add a section that scores the PR. Default is false.
- `require_tests_review`: if set to true, the tool will add a section that checks if the PR contains tests. Default is true.
- `require_security_review`: if set to true, the tool will add a section that checks if the PR contains security issues. Default is true.
- `require_estimate_effort_to_review`: if set to true, the tool will add a section that estimates thed effort needed to review the PR. Default is true.
#### general options
- `num_code_suggestions`: number of code suggestions provided by the 'review' tool. Default is 4.
- `inline_code_comments`: if set to true, the tool will publish the code suggestions as comments on the code diff. Default is false.
- `automatic_review`: if set to false, no automatic reviews will be done. Default is true.
- `remove_previous_review_comment`: if set to true, the tool will remove the previous review comment before adding a new one. Default is false.
- `persistent_comment`: if set to true, the review comment will be persistent. Default is true.
- `persistent_comment`: if set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".
- To enable `custom labels`, apply the configuration changes described [here](./GENERATE_CUSTOM_LABELS.md#configuration-changes)
#### Incremental Mode
Expand Down
2 changes: 1 addition & 1 deletion pr_agent/algo/pr_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")

for file in diff_files:
if file.filename.strip() == relevant_file:
if file.filename and (file.filename.strip() == relevant_file):
patch = file.patch
patch_lines = patch.splitlines()

Expand Down
20 changes: 16 additions & 4 deletions pr_agent/git_providers/bitbucket_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,29 @@ def get_diff_files(self) -> list[FilePatchInfo]:
self.diff_files = diff_files
return diff_files

def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
def get_latest_commit_url(self):
return self.pr.data['source']['commit']['links']['html']['href']

def get_comment_url(self, comment):
return comment.data['links']['html']['href']

def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool = True):
try:
for comment in self.pr.comments():
body = comment.raw
if initial_text in body:
if updated_text:
pr_comment_updated = pr_comment.replace(initial_text, updated_text)
if initial_header in body:
latest_commit_url = self.get_latest_commit_url()
comment_url = self.get_comment_url(comment)
if update_header:
updated_header = f"{initial_header}\n\n### (review updated until commit {latest_commit_url})\n"
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
else:
pr_comment_updated = pr_comment
get_logger().info(f"Persistent mode- updating comment {comment_url} to latest review message")
d = {"content": {"raw": pr_comment_updated}}
response = comment._update_data(comment.put(None, data=d))
self.publish_comment(
f"**[Persistent review]({comment_url})** updated to latest commit {latest_commit_url}")
return
except Exception as e:
get_logger().exception(f"Failed to update persistent review, error: {e}")
Expand Down
83 changes: 46 additions & 37 deletions pr_agent/git_providers/git_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,45 +40,10 @@ def get_diff_files(self) -> list[FilePatchInfo]:
def publish_description(self, pr_title: str, pr_body: str):
pass

@abstractmethod
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
pass

def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
self.publish_comment(pr_comment)

@abstractmethod
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
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
def publish_code_suggestions(self, code_suggestions: list) -> bool:
pass

@abstractmethod
def publish_labels(self, labels):
pass

@abstractmethod
def get_labels(self):
pass

@abstractmethod
def remove_initial_comment(self):
pass

@abstractmethod
def remove_comment(self, comment):
pass

@abstractmethod
def get_languages(self):
pass
Expand Down Expand Up @@ -116,12 +81,55 @@ def get_user_description(self) -> str:
# otherwise, extract the original user description from the existing pr-agent description and return it
return description.split("## User Description:", 1)[1].strip()

@abstractmethod
def get_repo_settings(self):
pass

def get_pr_id(self):
return ""

#### comments operations ####
@abstractmethod
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
pass

def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool):
self.publish_comment(pr_comment)

@abstractmethod
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
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
def remove_initial_comment(self):
pass

@abstractmethod
def remove_comment(self, comment):
pass

@abstractmethod
def get_issue_comments(self):
pass

def get_comment_url(self, comment) -> str:
return ""

#### labels operations ####
@abstractmethod
def get_repo_settings(self):
def publish_labels(self, labels):
pass

@abstractmethod
def get_labels(self):
pass

@abstractmethod
Expand All @@ -132,11 +140,12 @@ def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]:
def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool:
pass

#### commits operations ####
@abstractmethod
def get_commit_messages(self):
pass

def get_pr_id(self):
def get_latest_commit_url(self) -> str:
return ""

def get_main_pr_language(languages, files) -> str:
Expand Down
20 changes: 16 additions & 4 deletions pr_agent/git_providers/github_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,28 @@ def get_diff_files(self) -> list[FilePatchInfo]:
def publish_description(self, pr_title: str, pr_body: str):
self.pr.edit(title=pr_title, body=pr_body)

def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
def get_latest_commit_url(self) -> str:
return self.last_commit_id.html_url

def get_comment_url(self, comment) -> str:
return comment.html_url

def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool = True):
prev_comments = list(self.pr.get_issue_comments())
for comment in prev_comments:
body = comment.body
if body.startswith(initial_text):
if updated_text:
pr_comment_updated = pr_comment.replace(initial_text, updated_text)
if body.startswith(initial_header):
latest_commit_url = self.get_latest_commit_url()
comment_url = self.get_comment_url(comment)
if update_header:
updated_header = f"{initial_header}\n\n### (review updated until commit {latest_commit_url})\n"
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
else:
pr_comment_updated = pr_comment
get_logger().info(f"Persistent mode- updating comment {comment_url} to latest review message")
response = comment.edit(pr_comment_updated)
self.publish_comment(
f"**[Persistent review]({comment_url})** updated to latest commit {latest_commit_url}")
return
self.publish_comment(pr_comment)

Expand Down
20 changes: 16 additions & 4 deletions pr_agent/git_providers/gitlab_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,27 @@ def publish_description(self, pr_title: str, pr_body: str):
except Exception as e:
get_logger().exception(f"Could not update merge request {self.id_mr} description: {e}")

def publish_persistent_comment(self, pr_comment: str, initial_text: str, updated_text: str):
def get_latest_commit_url(self):
return self.mr.commits().next().web_url

def get_comment_url(self, comment):
return f"{self.mr.web_url}#note_{comment.id}"

def publish_persistent_comment(self, pr_comment: str, initial_header: str, update_header: bool = True):
try:
for comment in self.mr.notes.list(get_all=True)[::-1]:
if comment.body.startswith(initial_text):
if updated_text:
pr_comment_updated = pr_comment.replace(initial_text, updated_text)
if comment.body.startswith(initial_header):
latest_commit_url = self.get_latest_commit_url()
comment_url = self.get_comment_url(comment)
if update_header:
updated_header = f"{initial_header}\n\n### (review updated until commit {latest_commit_url})\n"
pr_comment_updated = pr_comment.replace(initial_header, updated_header)
else:
pr_comment_updated = pr_comment
get_logger().info(f"Persistent mode- updating comment {comment_url} to latest review message")
response = self.mr.notes.update(comment.id, {'body': pr_comment_updated})
self.publish_comment(
f"**[Persistent review]({comment_url})** updated to latest commit {latest_commit_url}")
return
except Exception as e:
get_logger().exception(f"Failed to update persistent review, error: {e}")
Expand Down
3 changes: 3 additions & 0 deletions pr_agent/tools/pr_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ async def _get_prediction(self, model: str) -> str:
user=user_prompt
)

if get_settings().config.verbosity_level >= 2:
get_logger().info(f"\nAI response:\n{response}")

return response

def _prepare_data(self):
Expand Down
7 changes: 5 additions & 2 deletions pr_agent/tools/pr_reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ async def run(self) -> None:
# publish the review
if get_settings().pr_reviewer.persistent_comment and not self.incremental.is_incremental:
self.git_provider.publish_persistent_comment(pr_comment,
initial_text="## PR Analysis",
updated_text="## PR Analysis (updated)")
initial_header="## PR Analysis",
update_header=True)
else:
self.git_provider.publish_comment(pr_comment)

Expand Down Expand Up @@ -178,6 +178,9 @@ async def _get_prediction(self, model: str) -> str:
user=user_prompt
)

if get_settings().config.verbosity_level >= 2:
get_logger().info(f"\nAI response:\n{response}")

return response

def _prepare_pr_review(self) -> str:
Expand Down
Loading