From 75c4befadf901d6e214aacf779ed904505566028 Mon Sep 17 00:00:00 2001 From: idubnori Date: Tue, 9 Apr 2024 22:51:02 +0900 Subject: [PATCH 1/7] feat: set review data to github actions output --- pr_agent/algo/utils.py | 10 ++++++++ pr_agent/servers/github_action_runner.py | 2 ++ pr_agent/settings/configuration.toml | 1 + pr_agent/tools/pr_reviewer.py | 3 ++- tests/unittest/test_github_output.py | 31 ++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/unittest/test_github_output.py diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index c2b6323c..8d3c9c01 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -2,6 +2,7 @@ from __future__ import annotations import difflib import json +import os import re import textwrap from datetime import datetime @@ -661,3 +662,12 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo], absolute_position = start2 + delta - 1 break return position, absolute_position + +def github_output(output_data: dict, key_name: str): + if get_settings().github_action_config.enable_output is False: + return + + output = os.getenv('GITHUB_OUTPUT') + key_data = output_data.get(key_name, {}) + with open(output, 'w') as f: + f.write(f"{key_name}={json.dumps(key_data, indent=None, ensure_ascii=False)}") diff --git a/pr_agent/servers/github_action_runner.py b/pr_agent/servers/github_action_runner.py index d4278156..6f9b6275 100644 --- a/pr_agent/servers/github_action_runner.py +++ b/pr_agent/servers/github_action_runner.py @@ -59,6 +59,8 @@ async def run_action(): get_settings().set("OPENAI.ORG", OPENAI_ORG) get_settings().set("GITHUB.USER_TOKEN", GITHUB_TOKEN) get_settings().set("GITHUB.DEPLOYMENT_TYPE", "user") + enable_output = get_setting_or_env("GITHUB_ACTION_CONFIG.ENABLE_OUTPUT", False) + get_settings().set("GITHUB_ACTION_CONFIG.ENABLE_OUTPUT", enable_output) # Load the event payload try: diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 837c71d3..0f43ccd8 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -133,6 +133,7 @@ try_fix_invalid_inline_comments = true # auto_review = true # set as env var in .github/workflows/pr-agent.yaml # auto_describe = true # set as env var in .github/workflows/pr-agent.yaml # auto_improve = true # set as env var in .github/workflows/pr-agent.yaml +# enable_output = true # set as env var in .github/workflows/pr-agent.yaml [github_app] # these toggles allows running the github app from custom deployments diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index 4d1739a5..38c2ef5c 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -8,7 +8,7 @@ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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, load_yaml, ModelType +from pr_agent.algo.utils import convert_to_markdown, github_output, load_yaml, ModelType from pr_agent.config_loader import get_settings from pr_agent.git_providers import get_git_provider from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language @@ -192,6 +192,7 @@ class PRReviewer: data = load_yaml(self.prediction.strip(), keys_fix_yaml=["estimated_effort_to_review_[1-5]:", "security_concerns:", "possible_issues:", "relevant_file:", "relevant_line:", "suggestion:"]) + github_output(data, 'review') if 'code_feedback' in data: code_feedback = data['code_feedback'] diff --git a/tests/unittest/test_github_output.py b/tests/unittest/test_github_output.py new file mode 100644 index 00000000..2ac3d2c7 --- /dev/null +++ b/tests/unittest/test_github_output.py @@ -0,0 +1,31 @@ +import os +import json +from pr_agent.algo.utils import get_settings, github_output + +class TestGitHubOutput: + def test_github_output_enabled(self, monkeypatch, tmp_path): + get_settings().set('GITHUB_ACTION_CONFIG.ENABLE_OUTPUT', True) + monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) + output_data = {'key1': {'value1': 1, 'value2': 2}} + key_name = 'key1' + + github_output(output_data, key_name) + + with open(str(tmp_path / 'output'), 'r') as f: + env_value = f.read() + + actual_key = env_value.split('=')[0] + actual_data = json.loads(env_value.split('=')[1]) + + assert actual_key == key_name + assert actual_data == output_data[key_name] + + def test_github_output_disabled(self, monkeypatch, tmp_path): + get_settings().set('GITHUB_ACTION_CONFIG.ENABLE_OUTPUT', False) + monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) + output_data = {'key1': {'value1': 1, 'value2': 2}} + key_name = 'key1' + + github_output(output_data, key_name) + + assert not os.path.exists(str(tmp_path / 'output')) \ No newline at end of file From 45176ab8932a07ba32cb226f340301a1003dc4fa Mon Sep 17 00:00:00 2001 From: idubnori Date: Wed, 10 Apr 2024 09:17:29 +0900 Subject: [PATCH 2/7] test: add config not set case --- tests/unittest/test_github_output.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/unittest/test_github_output.py b/tests/unittest/test_github_output.py index 2ac3d2c7..a046ec7d 100644 --- a/tests/unittest/test_github_output.py +++ b/tests/unittest/test_github_output.py @@ -28,4 +28,13 @@ class TestGitHubOutput: github_output(output_data, key_name) + assert not os.path.exists(str(tmp_path / 'output')) + + def test_github_output_notset(self, monkeypatch, tmp_path): + monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) + output_data = {'key1': {'value1': 1, 'value2': 2}} + key_name = 'key1' + + github_output(output_data, key_name) + assert not os.path.exists(str(tmp_path / 'output')) \ No newline at end of file From 3412095d81844f97abc6146603ceea1d584ea279 Mon Sep 17 00:00:00 2001 From: idubnori Date: Wed, 10 Apr 2024 09:18:21 +0900 Subject: [PATCH 3/7] chore: change default to true, if use in github actions --- pr_agent/servers/github_action_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/servers/github_action_runner.py b/pr_agent/servers/github_action_runner.py index 6f9b6275..ad16958e 100644 --- a/pr_agent/servers/github_action_runner.py +++ b/pr_agent/servers/github_action_runner.py @@ -59,7 +59,7 @@ async def run_action(): get_settings().set("OPENAI.ORG", OPENAI_ORG) get_settings().set("GITHUB.USER_TOKEN", GITHUB_TOKEN) get_settings().set("GITHUB.DEPLOYMENT_TYPE", "user") - enable_output = get_setting_or_env("GITHUB_ACTION_CONFIG.ENABLE_OUTPUT", False) + enable_output = get_setting_or_env("GITHUB_ACTION_CONFIG.ENABLE_OUTPUT", True) get_settings().set("GITHUB_ACTION_CONFIG.ENABLE_OUTPUT", enable_output) # Load the event payload From 97dcb34d771f0b8bb50e7d953cb23eb81487c761 Mon Sep 17 00:00:00 2001 From: idubnori Date: Wed, 10 Apr 2024 22:16:09 +0900 Subject: [PATCH 4/7] clean: rename to github_action_output --- pr_agent/algo/utils.py | 2 +- pr_agent/tools/pr_reviewer.py | 4 ++-- tests/unittest/test_github_output.py | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 8d3c9c01..c719c240 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -663,7 +663,7 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo], break return position, absolute_position -def github_output(output_data: dict, key_name: str): +def github_action_output(output_data: dict, key_name: str): if get_settings().github_action_config.enable_output is False: return diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index 38c2ef5c..53f7b0c0 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -8,7 +8,7 @@ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler 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_output, load_yaml, ModelType +from pr_agent.algo.utils import convert_to_markdown, github_action_output, load_yaml, ModelType from pr_agent.config_loader import get_settings from pr_agent.git_providers import get_git_provider from pr_agent.git_providers.git_provider import IncrementalPR, get_main_pr_language @@ -192,7 +192,7 @@ class PRReviewer: data = load_yaml(self.prediction.strip(), keys_fix_yaml=["estimated_effort_to_review_[1-5]:", "security_concerns:", "possible_issues:", "relevant_file:", "relevant_line:", "suggestion:"]) - github_output(data, 'review') + github_action_output(data, 'review') if 'code_feedback' in data: code_feedback = data['code_feedback'] diff --git a/tests/unittest/test_github_output.py b/tests/unittest/test_github_output.py index a046ec7d..10a2a02e 100644 --- a/tests/unittest/test_github_output.py +++ b/tests/unittest/test_github_output.py @@ -1,15 +1,15 @@ import os import json -from pr_agent.algo.utils import get_settings, github_output +from pr_agent.algo.utils import get_settings, github_action_output class TestGitHubOutput: - def test_github_output_enabled(self, monkeypatch, tmp_path): + def test_github_action_output_enabled(self, monkeypatch, tmp_path): get_settings().set('GITHUB_ACTION_CONFIG.ENABLE_OUTPUT', True) monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) output_data = {'key1': {'value1': 1, 'value2': 2}} key_name = 'key1' - github_output(output_data, key_name) + github_action_output(output_data, key_name) with open(str(tmp_path / 'output'), 'r') as f: env_value = f.read() @@ -20,21 +20,21 @@ class TestGitHubOutput: assert actual_key == key_name assert actual_data == output_data[key_name] - def test_github_output_disabled(self, monkeypatch, tmp_path): + def test_github_action_output_disabled(self, monkeypatch, tmp_path): get_settings().set('GITHUB_ACTION_CONFIG.ENABLE_OUTPUT', False) monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) output_data = {'key1': {'value1': 1, 'value2': 2}} key_name = 'key1' - github_output(output_data, key_name) + github_action_output(output_data, key_name) assert not os.path.exists(str(tmp_path / 'output')) - def test_github_output_notset(self, monkeypatch, tmp_path): + def test_github_action_output_notset(self, monkeypatch, tmp_path): monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) output_data = {'key1': {'value1': 1, 'value2': 2}} key_name = 'key1' - github_output(output_data, key_name) + github_action_output(output_data, key_name) assert not os.path.exists(str(tmp_path / 'output')) \ No newline at end of file From ae633b3cea456c9a6d155e9311b05b494249683f Mon Sep 17 00:00:00 2001 From: idubnori Date: Wed, 10 Apr 2024 22:30:16 +0900 Subject: [PATCH 5/7] refine: github_action_output --- pr_agent/algo/utils.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index c719c240..a1aaf50a 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -664,10 +664,13 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo], return position, absolute_position def github_action_output(output_data: dict, key_name: str): - if get_settings().github_action_config.enable_output is False: - return - - output = os.getenv('GITHUB_OUTPUT') - key_data = output_data.get(key_name, {}) - with open(output, 'w') as f: - f.write(f"{key_name}={json.dumps(key_data, indent=None, ensure_ascii=False)}") + try: + if not get_settings().get('github_action_config.enable_output', False): + return + + key_data = output_data.get(key_name, {}) + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f"{key_name}={json.dumps(key_data, indent=None, ensure_ascii=False)}", file=fh) + except Exception as e: + get_logger().error(f"Failed to write to GitHub Action output: {e}") + return \ No newline at end of file From 5e5ead98dea6598459833d750b9ca76829b1f447 Mon Sep 17 00:00:00 2001 From: idubnori Date: Wed, 10 Apr 2024 22:36:15 +0900 Subject: [PATCH 6/7] test: rename & add error case --- ...t_github_output.py => test_github_action_output.py} | 10 ++++++++++ 1 file changed, 10 insertions(+) rename tests/unittest/{test_github_output.py => test_github_action_output.py} (77%) diff --git a/tests/unittest/test_github_output.py b/tests/unittest/test_github_action_output.py similarity index 77% rename from tests/unittest/test_github_output.py rename to tests/unittest/test_github_action_output.py index 10a2a02e..2b8e0db1 100644 --- a/tests/unittest/test_github_output.py +++ b/tests/unittest/test_github_action_output.py @@ -31,10 +31,20 @@ class TestGitHubOutput: assert not os.path.exists(str(tmp_path / 'output')) def test_github_action_output_notset(self, monkeypatch, tmp_path): + # not set config monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) output_data = {'key1': {'value1': 1, 'value2': 2}} key_name = 'key1' github_action_output(output_data, key_name) + assert not os.path.exists(str(tmp_path / 'output')) + + def test_github_action_output_error_case(self, monkeypatch, tmp_path): + monkeypatch.setenv('GITHUB_OUTPUT', str(tmp_path / 'output')) + output_data = None # invalid data + key_name = 'key1' + + github_action_output(output_data, key_name) + assert not os.path.exists(str(tmp_path / 'output')) \ No newline at end of file From aef1c6ecde14c367662e6bce34b1d489fa567847 Mon Sep 17 00:00:00 2001 From: idubnori Date: Wed, 10 Apr 2024 23:04:29 +0900 Subject: [PATCH 7/7] docs: add feature spec and config --- docs/docs/usage-guide/automations_and_usage.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/usage-guide/automations_and_usage.md b/docs/docs/usage-guide/automations_and_usage.md index d20516fd..15b52402 100644 --- a/docs/docs/usage-guide/automations_and_usage.md +++ b/docs/docs/usage-guide/automations_and_usage.md @@ -115,10 +115,15 @@ Specifically, start by setting the following environment variables: github_action_config.auto_review: "true" # enable\disable auto review github_action_config.auto_describe: "true" # enable\disable auto describe github_action_config.auto_improve: "true" # enable\disable auto improve + github_action_config.enable_output: "true" # enable\disable github actions output parameter ``` `github_action_config.auto_review`, `github_action_config.auto_describe` and `github_action_config.auto_improve` are used to enable/disable automatic tools that run when a new PR is opened. If not set, the default configuration is for all three tools to run automatically when a new PR is opened. +`github_action_config.enable_output` are used to enable/disable github actions [output parameter](https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-docker-container-and-javascript-actions) (default is `true`). +Review result is output as JSON to `steps.{step-id}.outputs.review` property. +The JSON structure is equivalent to the yaml data structure defined in [pr_reviewer_prompts.toml](https://github.com/idubnori/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml). + Note that you can give additional config parameters by adding environment variables to `.github/workflows/pr_agent.yml`, or by using a `.pr_agent.toml` file in the root of your repo, similar to the GitHub App usage. For example, you can set an environment variable: `pr_description.add_original_user_description=false`, or add a `.pr_agent.toml` file with the following content: