diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 73e0a8f7..0b21c9df 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -31,7 +31,7 @@ def get_setting(key: str) -> Any: return global_settings.get(key, None) -def emphasize_header(text: str) -> str: +def emphasize_header(text: str, only_markdown=False) -> str: try: # Finding the position of the first occurrence of ": " colon_position = text.find(": ") @@ -39,7 +39,10 @@ def emphasize_header(text: str) -> str: # Splitting the string and wrapping the first part in tags if colon_position != -1: # Everything before the colon (inclusive) is wrapped in tags - transformed_string = "" + text[:colon_position + 1] + "" +'
' + text[colon_position + 1:] + if only_markdown: + transformed_string = f"**{text[:colon_position + 1]}**\n" + text[colon_position + 1:] + else: + transformed_string = "" + text[:colon_position + 1] + "" +'
' + text[colon_position + 1:] else: # If there's no ": ", return the original string transformed_string = text @@ -164,6 +167,110 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool = True, increment return markdown_text +def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, incremental_review=None) -> str: + """ + Convert a dictionary of data into markdown format. + Args: + output_data (dict): A dictionary containing data to be converted to markdown format. + Returns: + str: The markdown formatted text generated from the input dictionary. + """ + + emojis = { + "Can be split": "๐Ÿ”€", + "Possible issues": "โšก", + "Key issues to review": "โšก", + "Score": "๐Ÿ…", + "Relevant tests": "๐Ÿงช", + "Focused PR": "โœจ", + "Relevant ticket": "๐ŸŽซ", + "Security concerns": "๐Ÿ”’", + "Insights from user's answers": "๐Ÿ“", + "Code feedback": "๐Ÿค–", + "Estimated effort to review [1-5]": "โฑ๏ธ", + } + markdown_text = "" + if not incremental_review: + markdown_text += f"## PR Reviewer Guide ๐Ÿ”\n\n" + else: + markdown_text += f"## Incremental PR Reviewer Guide ๐Ÿ”\n\n" + markdown_text += f"โฎ๏ธ Review for commits since previous PR-Agent review {incremental_review}.\n\n" + + # if not output_data or not output_data.get('review', {}): + # return "" + + for key, value in output_data['review'].items(): + if value is None or value == '' or value == {} or value == []: + if key.lower() != 'can_be_split': + continue + key_nice = key.replace('_', ' ').capitalize() + emoji = emojis.get(key_nice, "") + if 'Estimated effort to review' in key_nice: + key_nice = 'Estimated effort to review [1-5]' + value_int = int(value) + blue_bars = '๐Ÿ”ต' * value_int + white_bars = 'โšช' * (5 - value_int) + value = f"{value.strip()} {blue_bars}{white_bars}" + markdown_text += f"### {emoji} {key_nice}: {value}\n\n" + elif 'relevant tests' in key_nice.lower(): + value = value.strip().lower() + if is_value_no(value): + markdown_text += f'### {emoji} No relevant tests\n\n' + else: + markdown_text += f"### PR contains tests\n\n" + elif 'security concerns' in key_nice.lower(): + if is_value_no(value): + markdown_text += f'### {emoji} No security concerns identified\n\n' + else: + markdown_text += f"### {emoji} Security concerns\n\n" + value = emphasize_header(value.strip()) + markdown_text += f"{value}\n\n" + elif 'can be split' in key_nice.lower(): + markdown_text += process_can_be_split(emoji, value) + elif 'key issues to review' in key_nice.lower(): + value = value.strip() + issues = value.split('\n- ') + for i, _ in enumerate(issues): + issues[i] = issues[i].strip().strip('-').strip() + issues = unique_strings(issues) # remove duplicates + markdown_text += f"### {emoji} Key issues to review\n\n" + for i, issue in enumerate(issues): + if not issue: + continue + issue = emphasize_header(issue, only_markdown=True) + markdown_text += f"{issue}\n\n" + # else: + # value = emphasize_header(value.strip('-').strip()) + # # value = replace_code_tags(value) + # # markdown_text += f" {emoji} {key_nice}\n{value}\n\n\n" + # markdown_text += f"### {emoji} {key_nice}: {value}\n\n" + else: + # markdown_text += f" {emoji} {key_nice}\n{value}\n\n\n" + markdown_text += f"### {emoji} {key_nice}: {value}\n\n" + print(markdown_text) + + + if 'code_feedback' in output_data: + if gfm_supported: + markdown_text += f"\n\n" + markdown_text += f"
Code feedback:\n\n" + markdown_text += "
" + else: + markdown_text += f"\n\n** Code feedback:**\n\n" + for i, value in enumerate(output_data['code_feedback']): + if value is None or value == '' or value == {} or value == []: + continue + markdown_text += parse_code_suggestion(value, i, gfm_supported)+"\n\n" + if markdown_text.endswith('
'): + markdown_text= markdown_text[:-4] + if gfm_supported: + markdown_text += f"
" + #print(markdown_text) + + + return markdown_text + + def process_can_be_split(emoji, value): try: # key_nice = "Can this PR be split?" @@ -171,29 +278,46 @@ def process_can_be_split(emoji, value): markdown_text = "" if not value or isinstance(value, list) and len(value) == 1: value = "No" - markdown_text += f" {emoji} {key_nice}\n\n{value}\n\n\n" + # markdown_text += f" {emoji} {key_nice}\n\n{value}\n\n\n" + markdown_text += f"### {emoji} No multiple PR themes\n\n" else: - number_of_splits = len(value) - markdown_text += f" {emoji} {key_nice}\n" + markdown_text += f"### {emoji} {key_nice}\n\n" for i, split in enumerate(value): title = split.get('title', '') relevant_files = split.get('relevant_files', []) - if i == 0: - markdown_text += f"
\nSub-PR theme:
{title}
\n\n" - markdown_text += f"
\n" - markdown_text += f"Relevant files:\n" - markdown_text += f"
    \n" - for file in relevant_files: - markdown_text += f"
  • {file}
  • \n" - markdown_text += f"
\n\n
\n" - else: - markdown_text += f"\n
\nSub-PR theme:
{title}
\n\n" - markdown_text += f"
\n" - markdown_text += f"Relevant files:\n" - markdown_text += f"
    \n" - for file in relevant_files: - markdown_text += f"
  • {file}
  • \n" - markdown_text += f"
\n\n
\n" + markdown_text += f"
\nSub-PR theme: {title}\n\n" + markdown_text += f"___\n\nRelevant files:\n\n" + for file in relevant_files: + markdown_text += f"- {file}\n" + markdown_text += f"___\n\n" + markdown_text += f"
\n\n" + + # markdown_text += f"#### Sub-PR theme: {title}\n\n" + # markdown_text += f"Relevant files:\n\n" + # for file in relevant_files: + # markdown_text += f"- {file}\n" + # markdown_text += "\n" + # number_of_splits = len(value) + # markdown_text += f" {emoji} {key_nice}\n" + # for i, split in enumerate(value): + # title = split.get('title', '') + # relevant_files = split.get('relevant_files', []) + # if i == 0: + # markdown_text += f"
\nSub-PR theme:
{title}
\n\n" + # markdown_text += f"
\n" + # markdown_text += f"Relevant files:\n" + # markdown_text += f"
    \n" + # for file in relevant_files: + # markdown_text += f"
  • {file}
  • \n" + # markdown_text += f"
\n\n
\n" + # else: + # markdown_text += f"\n
\nSub-PR theme:
{title}
\n\n" + # markdown_text += f"
\n" + # markdown_text += f"Relevant files:\n" + # markdown_text += f"
    \n" + # for file in relevant_files: + # markdown_text += f"
  • {file}
  • \n" + # markdown_text += f"
\n\n
\n" except Exception as e: get_logger().exception(f"Failed to process can be split: {e}") return "" @@ -772,3 +896,11 @@ def show_relevant_configurations(relevant_section: str) -> str: markdown_text += "\n```" markdown_text += "\n\n" return markdown_text + +def is_value_no(value): + if value is None: + return True + value_str = str(value).strip().lower() + if value_str == 'no' or value_str == 'none' or value_str == 'false': + return True + return False diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index 35abe537..bf050dc5 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -9,7 +9,7 @@ from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.utils import convert_to_markdown, github_action_output, load_yaml, ModelType, \ - show_relevant_configurations + show_relevant_configurations, convert_to_markdown_v2 from pr_agent.config_loader import get_settings from pr_agent.git_providers import get_git_provider, get_git_provider_with_context from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language @@ -230,7 +230,7 @@ class PRReviewer: f"{self.git_provider.incremental.first_new_commit_sha}" incremental_review_markdown_text = f"Starting from commit {last_commit_url}" - markdown_text = convert_to_markdown(data, self.git_provider.is_supported("gfm_markdown"), + markdown_text = convert_to_markdown_v2(data, self.git_provider.is_supported("gfm_markdown"), incremental_review_markdown_text) # Add help text if gfm_markdown is supported