Skip to content

Commit

Permalink
Enable GitAuto to react to PR review comments, letting users make pre…
Browse files Browse the repository at this point in the history
…cise adjustments without rerunning from the issue, dodging randomness.
  • Loading branch information
hiroshinishio committed Jan 3, 2025
1 parent 16542e1 commit d745fe6
Show file tree
Hide file tree
Showing 11 changed files with 1,507 additions and 40 deletions.
570 changes: 570 additions & 0 deletions payloads/pull_request_review_comment/created.json

Large diffs are not rendered by default.

575 changes: 575 additions & 0 deletions payloads/pull_request_review_comment/edited.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions services/check_run_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
PullRequest,
Repository,
)
from services.github.pulls_manager import get_pull_request, get_pull_request_files
from services.github.pulls_manager import get_pull_request, get_pull_request_file_changes
from services.github.github_utils import create_permission_url
from services.openai.commit_changes import chat_with_agent
from services.openai.chat import chat_with_ai
Expand Down Expand Up @@ -137,7 +137,7 @@ def handle_check_run(payload: CheckRunCompletedPayload) -> None:
update_comment(body=comment_body, base_args=base_args, p=5)
pull_title, pull_body = get_pull_request(url=pull_url, token=token)
pull_file_url = f"{pull_url}/files"
pull_changes = get_pull_request_files(url=pull_file_url, token=token)
pull_changes = get_pull_request_file_changes(url=pull_file_url, token=token)

# Get the GitHub workflow file content
comment_body = "Checking out the GitHub Action workflow file..."
Expand Down
2 changes: 1 addition & 1 deletion services/gitauto_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

async def handle_gitauto(
payload: GitHubLabeledPayload,
trigger_type: str,
trigger_type: Literal["label", "comment", "review_comment"],
input_from: Literal["github", "jira"],
) -> None:
"""Core functionality to create comments on issue, create PRs, and update progress."""
Expand Down
14 changes: 13 additions & 1 deletion services/github/comment_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from requests import delete, get
from requests import delete, get, post
from config import GITHUB_API_URL, TIMEOUT, GITHUB_APP_USER_NAME
from constants.messages import COMPLETED_PR
from services.github.create_headers import create_headers
Expand Down Expand Up @@ -55,3 +55,15 @@ def delete_my_comments(base_args: BaseArgs):
# print(f"My comments: {dumps(obj=my_comments, indent=2)}")
for comment in my_comments:
delete_a_comment(base_args=base_args, comment_id=comment["id"])


def reply_to_comment(base_args: BaseArgs, body: str):
"""https://docs.github.com/en/rest/pulls/comments?apiVersion=2022-11-28#create-a-reply-for-a-review-comment"""
owner, repo, token = base_args["owner"], base_args["repo"], base_args["token"]
pull_number = base_args["pull_number"]
comment_id = base_args["review_id"]
url = f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies"
headers: dict[str, str] = create_headers(token=token)
response = post(url=url, headers=headers, json={"body": body}, timeout=TIMEOUT)
response.raise_for_status()
return response.json()["url"]
2 changes: 1 addition & 1 deletion services/github/github_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
GitHubLabeledPayload,
IssueInfo,
)
from services.github.pulls_manager import add_reviewers
from services.github.reviewers_manager import add_reviewers
from services.openai.vision import describe_image
from services.supabase import SupabaseManager
from utils.detect_new_line import detect_line_break
Expand Down
65 changes: 30 additions & 35 deletions services/github/pulls_manager.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,12 @@
from json import dumps
import requests
from config import GITHUB_API_URL, TIMEOUT, PER_PAGE
from config import TIMEOUT, PER_PAGE
from services.github.create_headers import create_headers
from services.github.github_manager import get_remote_file_content
from services.github.github_types import BaseArgs
from services.github.user_manager import check_user_is_collaborator
from utils.handle_exceptions import handle_exceptions


@handle_exceptions(default_return_value=None, raise_on_error=False)
def add_reviewers(base_args: BaseArgs):
"""https://docs.github.com/en/rest/pulls/review-requests?apiVersion=2022-11-28#request-reviewers-for-a-pull-request"""
owner, repo, pr_number, token, reviewers = (
base_args["owner"],
base_args["repo"],
base_args["pr_number"],
base_args["token"],
base_args["reviewers"],
)

# Check if the reviewers are collaborators because reviewers must be collaborators
valid_reviewers: list[str] = []
for reviewer in reviewers:
is_collaborator = check_user_is_collaborator(
owner=owner, repo=repo, user=reviewer, token=token
)
if is_collaborator:
valid_reviewers.append(reviewer)

# If no valid reviewers, return
if not valid_reviewers:
return

# Add the reviewers to the pull request
url = f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers"
headers = create_headers(token=token)
json = {"reviewers": valid_reviewers}
response = requests.post(url=url, headers=headers, json=json, timeout=TIMEOUT)
response.raise_for_status()


@handle_exceptions(default_return_value=("", ""), raise_on_error=False)
def get_pull_request(url: str, token: str):
"""https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#get-a-pull-request"""
Expand All @@ -51,7 +20,33 @@ def get_pull_request(url: str, token: str):


@handle_exceptions(default_return_value=None, raise_on_error=False)
def get_pull_request_files(url: str, token: str):
def get_pull_request_file_contents(url: str, base_args: BaseArgs):
"""https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files"""
token = base_args["token"]
headers = create_headers(token=token)
contents: list[str] = []
page = 1
while True:
params = {"per_page": PER_PAGE, "page": page}
response = requests.get(
url=url, headers=headers, params=params, timeout=TIMEOUT
)
response.raise_for_status()
files = response.json()
if not files:
break
for file in files:
file_path = file["filename"]
content = get_remote_file_content(file_path=file_path, base_args=base_args)
contents.append(content)
page += 1

print(f"get_pull_request_file_contents: {dumps(obj=contents, indent=2)}")
return contents


@handle_exceptions(default_return_value=None, raise_on_error=False)
def get_pull_request_file_changes(url: str, token: str):
"""https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files"""
headers = create_headers(token=token)
changes: list[dict[str, str]] = []
Expand Down
38 changes: 38 additions & 0 deletions services/github/reviewers_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import requests
from config import GITHUB_API_URL, TIMEOUT
from services.github.create_headers import create_headers
from services.github.github_types import BaseArgs
from services.github.user_manager import check_user_is_collaborator
from utils.handle_exceptions import handle_exceptions


@handle_exceptions(default_return_value=None, raise_on_error=False)
def add_reviewers(base_args: BaseArgs):
"""https://docs.github.com/en/rest/pulls/review-requests?apiVersion=2022-11-28#request-reviewers-for-a-pull-request"""
owner, repo, pr_number, token, reviewers = (
base_args["owner"],
base_args["repo"],
base_args["pr_number"],
base_args["token"],
base_args["reviewers"],
)

# Check if the reviewers are collaborators because reviewers must be collaborators
valid_reviewers: list[str] = []
for reviewer in reviewers:
is_collaborator = check_user_is_collaborator(
owner=owner, repo=repo, user=reviewer, token=token
)
if is_collaborator:
valid_reviewers.append(reviewer)

# If no valid reviewers, return
if not valid_reviewers:
return

# Add the reviewers to the pull request
url = f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers"
headers = create_headers(token=token)
json = {"reviewers": valid_reviewers}
response = requests.post(url=url, headers=headers, json=json, timeout=TIMEOUT)
response.raise_for_status()
11 changes: 11 additions & 0 deletions services/openai/instructions/resolve_feedback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
RESOLVE_FEEDBACK = """
You are an top-class software engineer.
Given information such as a pull request title, body, changes, workflow file content, and check run error log, resolve the feedback and write a plan to fix the error in a language that is used in the input (e.g. the plan should be in English but if the input is mainly in Japanese for example, the plan should be in Japanese).
Output format would be like this:
## What the feedback is
## Where to change
## How to change
Each section should be concise and to the point. Should not be long.
"""
Loading

0 comments on commit d745fe6

Please sign in to comment.