persistent release notes

This commit is contained in:
Hussam.lawen
2024-07-03 16:38:13 +03:00
parent b05e15e9ec
commit 0c3940b6a7
5 changed files with 141 additions and 42 deletions

View File

@ -138,6 +138,34 @@ class GitProvider(ABC):
final_update_message=True): final_update_message=True):
self.publish_comment(pr_comment) self.publish_comment(pr_comment)
def publish_persistent_comment_full(self, pr_comment: str,
initial_header: str,
update_header: bool = True,
name='review',
final_update_message=True):
try:
prev_comments = list(self.get_issue_comments())
for comment in prev_comments:
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#### ({name.capitalize()} 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 {name} message")
# response = self.mr.notes.update(comment.id, {'body': pr_comment_updated})
self.edit_comment(comment, pr_comment_updated)
if final_update_message:
self.publish_comment(
f"**[Persistent {name}]({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}")
pass
self.publish_comment(pr_comment)
@abstractmethod @abstractmethod
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

View File

@ -231,24 +231,7 @@ class GithubProvider(GitProvider):
update_header: bool = True, update_header: bool = True,
name='review', name='review',
final_update_message=True): final_update_message=True):
prev_comments = list(self.pr.get_issue_comments()) self.publish_persistent_comment_full(pr_comment, initial_header, update_header, name, final_update_message)
for comment in prev_comments:
body = comment.body
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#### ({name.capitalize()} 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)
if final_update_message:
self.publish_comment(
f"**[Persistent {name}]({comment_url})** updated to latest commit {latest_commit_url}")
return
self.publish_comment(pr_comment)
def publish_comment(self, pr_comment: str, is_temporary: bool = False): def publish_comment(self, pr_comment: str, is_temporary: bool = False):
if is_temporary and not get_settings().config.publish_output_progress: if is_temporary and not get_settings().config.publish_output_progress:

View File

@ -173,26 +173,7 @@ class GitLabProvider(GitProvider):
update_header: bool = True, update_header: bool = True,
name='review', name='review',
final_update_message=True): final_update_message=True):
try: self.publish_persistent_comment_full(pr_comment, initial_header, update_header, name, final_update_message)
for comment in self.mr.notes.list(get_all=True)[::-1]:
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#### ({name.capitalize()} 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 {name} message")
response = self.mr.notes.update(comment.id, {'body': pr_comment_updated})
if final_update_message:
self.publish_comment(
f"**[Persistent {name}]({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}")
pass
self.publish_comment(pr_comment)
def publish_comment(self, mr_comment: str, is_temporary: bool = False): def publish_comment(self, mr_comment: str, is_temporary: bool = False):
comment = self.mr.notes.create({'body': mr_comment}) comment = self.mr.notes.create({'body': mr_comment})
@ -203,6 +184,11 @@ class GitLabProvider(GitProvider):
def edit_comment(self, comment, body: str): def edit_comment(self, comment, body: str):
self.mr.notes.update(comment.id,{'body': body} ) self.mr.notes.update(comment.id,{'body': body} )
def edit_comment_from_comment_id(self, comment_id: int, body: str):
comment = self.mr.notes.get(comment_id)
comment.body = body
comment.save()
def reply_to_comment_from_comment_id(self, comment_id: int, body: str): def reply_to_comment_from_comment_id(self, comment_id: int, body: str):
discussion = self.mr.discussions.get(comment_id) discussion = self.mr.discussions.get(comment_id)
discussion.notes.create({'body': body}) discussion.notes.create({'body': body})
@ -219,6 +205,10 @@ class GitLabProvider(GitProvider):
def create_inline_comments(self, comments: list[dict]): def create_inline_comments(self, comments: list[dict]):
raise NotImplementedError("Gitlab provider does not support publishing inline comments yet") raise NotImplementedError("Gitlab provider does not support publishing inline comments yet")
def get_comment_body_from_comment_id(self, comment_id: int):
comment = self.mr.notes.get(comment_id)
return comment
def send_inline_comment(self,body: str,edit_type: str,found: bool,relevant_file: str,relevant_line_in_file: int, def send_inline_comment(self,body: str,edit_type: str,found: bool,relevant_file: str,relevant_line_in_file: int,
source_line_no: int, target_file: str,target_line_no: int) -> None: source_line_no: int, target_file: str,target_line_no: int) -> None:
if not found: if not found:
@ -381,7 +371,7 @@ class GitLabProvider(GitProvider):
return self.mr.description return self.mr.description
def get_issue_comments(self): def get_issue_comments(self):
raise NotImplementedError("GitLab provider does not support issue comments yet") return self.mr.notes.list(get_all=True)[::-1]
def get_repo_settings(self): def get_repo_settings(self):
try: try:

View File

@ -92,7 +92,8 @@ commitable_code_suggestions = false
extra_instructions = "" extra_instructions = ""
rank_suggestions = false rank_suggestions = false
enable_help_text=false enable_help_text=false
persistent_comment=false persistent_comment=true
max_history_len=4
# enable to apply suggestion 💎 # enable to apply suggestion 💎
apply_suggestions_checkbox=true apply_suggestions_checkbox=true
# suggestions scoring # suggestions scoring

View File

@ -17,6 +17,7 @@ from pr_agent.log import get_logger
from pr_agent.servers.help import HelpMessage from pr_agent.servers.help import HelpMessage
from pr_agent.tools.pr_description import insert_br_after_x_chars from pr_agent.tools.pr_description import insert_br_after_x_chars
import difflib import difflib
import re
class PRCodeSuggestions: class PRCodeSuggestions:
@ -132,11 +133,12 @@ class PRCodeSuggestions:
if get_settings().pr_code_suggestions.persistent_comment: if get_settings().pr_code_suggestions.persistent_comment:
final_update_message = False final_update_message = False
self.git_provider.publish_persistent_comment(pr_body, self.publish_persistent_comment_with_history(pr_body,
initial_header="## PR Code Suggestions ✨", initial_header="## PR Code Suggestions ✨",
update_header=True, update_header=True,
name="suggestions", name="suggestions",
final_update_message=final_update_message, ) final_update_message = final_update_message,
max_previous_comments = get_settings().pr_code_suggestions.max_history_len)
if self.progress_response: if self.progress_response:
self.progress_response.delete() self.progress_response.delete()
else: else:
@ -160,6 +162,101 @@ class PRCodeSuggestions:
self.git_provider.publish_comment(f"Failed to generate code suggestions for PR") self.git_provider.publish_comment(f"Failed to generate code suggestions for PR")
except Exception as e: except Exception as e:
pass pass
def publish_persistent_comment_with_history(self, pr_comment: str,
initial_header: str,
update_header: bool = True,
name='review',
final_update_message=True,
max_previous_comments=4):
history_header = f"#### Previous Suggestions\n"
last_commit_num = self.git_provider.get_latest_commit_url().split('/')[-1][:7]
latest_suggestion_header = f"Latest Suggestions up to {last_commit_num}"
latest_commit_html_comment = f"<!-- {last_commit_num} -->"
found_comment = None
if max_previous_comments > 0:
try:
prev_comments = list(self.git_provider.get_issue_comments())
for comment in prev_comments:
if comment.body.startswith(initial_header):
prev_suggestions = comment.body
found_comment = comment
comment_url = self.git_provider.get_comment_url(comment)
if history_header.strip() not in comment.body:
# no history section
# extract everything between <table> and </table> in comment.body including <table> and </table>
table_index = comment.body.find("<table>")
if table_index == -1:
self.git_provider.edit_comment(comment, pr_comment)
continue
# find http link from comment.body[:table_index]
up_to_commit_txt = self.extract_link(comment.body[:table_index])
prev_suggestion_table = comment.body[table_index:comment.body.rfind("</table>") + len("</table>")]
tick = "" if "" in prev_suggestion_table else ""
# surround with details tag
prev_suggestion_table = f"<details><summary>{tick}{name.capitalize()}{up_to_commit_txt}</summary>\n<br>{prev_suggestion_table}\n\n</details>"
new_suggestion_table = pr_comment.replace(initial_header, "").strip()
pr_comment_updated = f"{initial_header}\n{latest_commit_html_comment}\n\n"
pr_comment_updated += f"{latest_suggestion_header}\n{new_suggestion_table}\n\n___\n\n"
pr_comment_updated += f"{history_header}{prev_suggestion_table}\n"
else:
# get the text of the previous suggestions until the latest commit
sections = prev_suggestions.split(history_header.strip())
latest_table = sections[0].strip()
prev_suggestion_table = sections[1].replace(history_header, "").strip()
# get text after the latest_suggestion_header in comment.body
table_ind = latest_table.find("<table>")
up_to_commit_txt = self.extract_link(latest_table[:table_ind])
latest_table = latest_table[table_ind:latest_table.rfind("</table>") + len("</table>")]
# enforce max_previous_comments
count = prev_suggestions.count(f"\n<details><summary>{name.capitalize()}")
count += prev_suggestions.count(f"\n<details><summary>✅ {name.capitalize()}")
if count >= max_previous_comments:
# remove the oldest suggestion
prev_suggestion_table = prev_suggestion_table[:prev_suggestion_table.rfind(f"<details><summary>{name.capitalize()} up to commit")]
tick = "" if "" in latest_table else ""
# Add to the prev_suggestions section
last_prev_table = f"\n<details><summary>{tick}{name.capitalize()}{up_to_commit_txt}</summary>\n<br>{latest_table}\n\n</details>"
prev_suggestion_table = last_prev_table + "\n" + prev_suggestion_table
new_suggestion_table = pr_comment.replace(initial_header, "").strip()
pr_comment_updated = f"{initial_header}\n"
pr_comment_updated += f"{latest_commit_html_comment}\n\n"
pr_comment_updated += f"{latest_suggestion_header}\n\n{new_suggestion_table}\n\n"
pr_comment_updated += "___\n\n"
pr_comment_updated += f"{history_header}\n"
pr_comment_updated += f"{prev_suggestion_table}\n"
get_logger().info(f"Persistent mode - updating comment {comment_url} to latest {name} message")
self.git_provider.edit_comment(comment, pr_comment_updated)
return
except Exception as e:
get_logger().exception(f"Failed to update persistent review, error: {e}")
pass
body = pr_comment.replace(initial_header, "").strip()
pr_comment = f"{initial_header}\n\n{latest_commit_html_comment}\n\n{body}\n\n"
if found_comment is not None:
self.git_provider.edit_comment(found_comment, pr_comment)
else:
self.git_provider.publish_comment(pr_comment)
def extract_link(self, s):
r = re.compile(r"<!--.*?-->")
match = r.search(s)
up_to_commit_txt = ""
if match:
up_to_commit_txt = f" up to commit {match.group(0)[4:-3].strip()}"
return up_to_commit_txt
async def _prepare_prediction(self, model: str) -> dict: async def _prepare_prediction(self, model: str) -> dict:
self.patches_diff = get_pr_diff(self.git_provider, self.patches_diff = get_pr_diff(self.git_provider,