Skip to content
This repository was archived by the owner on Dec 2, 2024. It is now read-only.
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
17 changes: 13 additions & 4 deletions .github/workflows/openhands-resolver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ on:
types: [labeled]
pull_request:
types: [labeled]
issue_comment:
types: [created]

permissions:
contents: write
Expand All @@ -31,7 +33,11 @@ permissions:

jobs:
auto-fix:
if: github.event_name == 'workflow_call' || github.event.label.name == 'fix-me' || github.event.label.name == 'fix-me-experimental'
if: |
github.event_name == 'workflow_call' ||
github.event.label.name == 'fix-me' ||
github.event.label.name == 'fix-me-experimental' ||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@openhands-agent'))
runs-on: ubuntu-latest
steps:
- name: Checkout repository
Expand Down Expand Up @@ -83,6 +89,8 @@ jobs:
echo "ISSUE_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV
echo "ISSUE_TYPE=issue" >> $GITHUB_ENV
fi

echo "COMMENT_ID=${{ github.event.comment.id || 'None' }}" >> $GITHUB_ENV
echo "MAX_ITERATIONS=${{ inputs.max_iterations || 50 }}" >> $GITHUB_ENV

- name: Comment on issue with start message
Expand Down Expand Up @@ -119,7 +127,8 @@ jobs:
--repo ${{ github.repository }} \
--issue-number ${{ env.ISSUE_NUMBER }} \
--issue-type ${{ env.ISSUE_TYPE }} \
--max-iterations ${{ env.MAX_ITERATIONS }}
--max-iterations ${{ env.MAX_ITERATIONS }} \
--comment-id ${{ env.COMMENT_ID }}

- name: Check resolution result
id: check_result
Expand All @@ -132,11 +141,11 @@ jobs:

- name: Upload output.jsonl as artifact
uses: actions/upload-artifact@v4
if: always() # Upload even if the previous steps fail
if: always() # Upload even if the previous steps fail
with:
name: resolver-output
path: /tmp/output/output.jsonl
retention-days: 30 # Keep the artifact for 30 days
retention-days: 30 # Keep the artifact for 30 days

- name: Create draft PR or push branch
env:
Expand Down
20 changes: 13 additions & 7 deletions openhands_resolver/issue_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class IssueHandlerInterface(ABC):
issue_type: ClassVar[str]

@abstractmethod
def get_converted_issues(self) -> list[GithubIssue]:
def get_converted_issues(self, comment_id: int | None = None) -> list[GithubIssue]:
"""Download issues from GitHub."""
pass

Expand Down Expand Up @@ -76,7 +76,7 @@ def _extract_image_urls(self, issue_body: str) -> list[str]:
image_pattern = r'!\[.*?\]\((https?://[^\s)]+)\)'
return re.findall(image_pattern, issue_body)

def _get_issue_comments(self, issue_number: int) -> list[str] | None:
def _get_issue_comments(self, issue_number: int, comment_id: int | None = None) -> list[str] | None:
"""Download comments for a specific issue from Github."""
url = f"https://api.github.com/repos/{self.owner}/{self.repo}/issues/{issue_number}/comments"
headers = {
Expand All @@ -94,18 +94,23 @@ def _get_issue_comments(self, issue_number: int) -> list[str] | None:
if not comments:
break

all_comments.extend([comment["body"] for comment in comments])
if comment_id:
matching_comment = next((comment["body"] for comment in comments if comment["id"] == comment_id), None)
if matching_comment:
return [matching_comment]
else:
all_comments.extend([comment["body"] for comment in comments])

params["page"] += 1

return all_comments if all_comments else None

def get_converted_issues(self) -> list[GithubIssue]:
def get_converted_issues(self, comment_id: int | None = None) -> list[GithubIssue]:
"""Download issues from Github.

Returns:
List of Github issues.
"""

all_issues = self._download_issues_from_github()
converted_issues = []
for issue in all_issues:
Expand All @@ -119,7 +124,7 @@ def get_converted_issues(self) -> list[GithubIssue]:
continue

# Get issue thread comments
thread_comments = self._get_issue_comments(issue["number"])
thread_comments = self._get_issue_comments(issue["number"], comment_id=comment_id)
# Convert empty lists to None for optional fields
issue_details = GithubIssue(
owner=self.owner,
Expand All @@ -132,6 +137,7 @@ def get_converted_issues(self) -> list[GithubIssue]:
)

converted_issues.append(issue_details)

return converted_issues

def get_instruction(self, issue: GithubIssue, prompt_template: str, repo_instruction: str | None = None) -> tuple[str, list[str]]:
Expand Down Expand Up @@ -332,7 +338,7 @@ def _get_pr_comments(self, pr_number: int) -> list[str] | None:

return all_comments if all_comments else None

def get_converted_issues(self) -> list[GithubIssue]:
def get_converted_issues(self, comment_id: int | None = None) -> list[GithubIssue]:
all_issues = self._download_issues_from_github()
converted_issues = []
for issue in all_issues:
Expand Down
18 changes: 17 additions & 1 deletion openhands_resolver/resolve_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ async def resolve_issue(
issue_type: str,
repo_instruction: str | None,
issue_number: int,
comment_id: int | None,
reset_logger: bool = False,
) -> None:
"""Resolve a single github issue.
Expand All @@ -322,7 +323,7 @@ async def resolve_issue(
issue_handler = issue_handler_factory(issue_type, owner, repo, token)

# Load dataset
issues: list[GithubIssue] = issue_handler.get_converted_issues()
issues: list[GithubIssue] = issue_handler.get_converted_issues(comment_id=comment_id)

# Find the specific issue
issue = next((i for i in issues if i.number == issue_number), None)
Expand Down Expand Up @@ -429,6 +430,13 @@ async def resolve_issue(
def main():
import argparse

def int_or_none(value):
if value.lower() == 'none':
return None
else:
return int(value)


parser = argparse.ArgumentParser(description="Resolve a single issue from Github.")
parser.add_argument(
"--repo",
Expand Down Expand Up @@ -466,6 +474,13 @@ def main():
required=True,
help="Issue number to resolve.",
)
parser.add_argument(
"--comment-id",
type=int_or_none,
required=False,
default=None,
help="Resolve a specific comment"
)
parser.add_argument(
"--output-dir",
type=str,
Expand Down Expand Up @@ -566,6 +581,7 @@ def main():
issue_type=issue_type,
repo_instruction=repo_instruction,
issue_number=my_args.issue_number,
comment_id=my_args.comment_id,
)
)

Expand Down
39 changes: 39 additions & 0 deletions tests/test_resolve_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,45 @@ def get_mock_response(url, *args, **kwargs):
assert not issues[0].closing_issues
assert not issues[0].thread_ids

def test_download_issue_with_specific_comment():
handler = IssueHandler("owner", "repo", "token")

# Define the specific comment_id to filter
specific_comment_id = 101

# Mock issue and comment responses
mock_issue_response = MagicMock()
mock_issue_response.json.side_effect = [
[
{"number": 1, "title": "Issue 1", "body": "This is an issue"},
],
None,
]
mock_issue_response.raise_for_status = MagicMock()

mock_comments_response = MagicMock()
mock_comments_response.json.return_value = [
{"id": specific_comment_id, "body": "Specific comment body", "issue_url": "https://api.github.com/repos/owner/repo/issues/1"},
{"id": 102, "body": "Another comment body", "issue_url": "https://api.github.com/repos/owner/repo/issues/2"},
]
mock_comments_response.raise_for_status = MagicMock()


def get_mock_response(url, *args, **kwargs):
if "/comments" in url:
return mock_comments_response

return mock_issue_response


with patch('requests.get', side_effect=get_mock_response):
issues = handler.get_converted_issues(comment_id=specific_comment_id)

assert len(issues) == 1
assert issues[0].number == 1
assert issues[0].title == "Issue 1"
assert issues[0].thread_comments == ["Specific comment body"]


if __name__ == "__main__":
pytest.main()
Expand Down