Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add first-class support for PRs, including reviews, review comments #20

Merged
merged 2 commits into from
Jan 30, 2024
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

Then, you can answer an issue or PR and add the label from the config, in this case, `answered`.

After 10 days, if no one has added a new comment, the GitHub action will write:
After 10 days, if no one has added a new comment (or in the case of PRs, a new review or commit), the GitHub action will write:

```markdown
Assuming the original need was handled, this will be automatically closed now.
Expand Down Expand Up @@ -140,7 +140,7 @@ And finally, if:

* a PR has a label `needs-tests`
* the label was added _after_ the last comment
* the last comment was addded more than `691200` seconds (8 days) ago
* the last comment was added more than `691200` seconds (8 days) ago

...the GitHub action would close the PR with:

Expand Down Expand Up @@ -336,7 +336,7 @@ on:
* The `issues` option with a type of `label` will run it with each specific issue when you add a label.
* This way you can add a label to an issue that was answered long ago, and if the configured delay since the last comment is enough the GitHub action will close the issue right away.
* The `pull_request_target` option with a type of `label` will run it with each specific Pull Request made to your repo when you add a label.
* This way you can add a label to a PR that was answered long ago, or that was waiting for more comments from the author, etc. And if the configured delay since the last comment is enough the GitHub action will close the issue right away.
* This way you can add a label to a PR that was answered long ago, or that was waiting for more comments from the author, reviews, commits, etc. And if the configured delay since the last comment is enough the GitHub action will close the issue right away.
* The `workflow_dispatch` option allows you to run the action manually from the GitHub Actions tab for your repo.

## Motivation
Expand Down
52 changes: 34 additions & 18 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
from datetime import datetime, timedelta, timezone
import logging
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Dict, List, Optional, Set

from github import Github
from github.Issue import Issue
from github.IssueComment import IssueComment
from github.IssueEvent import IssueEvent
from pydantic import BaseModel, SecretStr, validator
from pydantic_settings import BaseSettings


class KeywordMeta(BaseModel):
delay: timedelta = timedelta(days=10)
message: str = "Assuming the original need was handled, this will be automatically closed now."
message: str = (
"Assuming the original need was handled, this will be automatically closed now."
)
remove_label_on_comment: bool = True
remove_label_on_close: bool = False

Expand Down Expand Up @@ -41,15 +42,31 @@ class PartialGitHubEvent(BaseModel):
pull_request: Optional[PartialGitHubEventIssue] = None


def get_last_comment(issue: Issue) -> Optional[IssueComment]:
last_comment: Optional[IssueComment] = None
comment: IssueComment
for comment in issue.get_comments():
if not last_comment:
last_comment = comment
elif comment.created_at > last_comment.created_at:
last_comment = comment
return last_comment
def get_last_interaction_date(issue: Issue) -> Optional[datetime]:
last_date: Optional[datetime] = None
comments = list(issue.get_comments())
if issue.pull_request:
pr = issue.as_pull_request()
commits = list(pr.get_commits())
reviews = list(pr.get_reviews())
pr_comments = list(pr.get_comments())
interactions = comments + pr_comments
interaction_dates = [
interaction.created_at for interaction in interactions
]
interaction_dates.extend(
[commit.commit.author.date for commit in commits]
)
interaction_dates.extend([review.submitted_at for review in reviews])
else:
interactions = comments
interaction_dates = [interaction.created_at for interaction in interactions]
for item_date in interaction_dates:
if not last_date:
last_date = item_date
elif item_date > last_date:
last_date = item_date
return last_date


def get_labeled_events(events: List[IssueEvent]) -> List[IssueEvent]:
Expand Down Expand Up @@ -92,13 +109,12 @@ def process_issue(*, issue: Issue, settings: Settings) -> None:
label_strs = set([label.name for label in issue.get_labels()])
events = list(issue.get_events())
labeled_events = get_labeled_events(events)
last_comment = get_last_comment(issue)
last_date = get_last_interaction_date(issue)
now = datetime.now(timezone.utc)
for keyword, keyword_meta in settings.input_config.items():
# Check closable delay, if enough time passed and the issue could be closed
closable_delay = (
last_comment is None
or (now - keyword_meta.delay) > last_comment.created_at
last_date is None or (now - keyword_meta.delay) > last_date
)
# Check label, optionally removing it if there's a comment after adding it
if keyword in label_strs:
Expand All @@ -107,9 +123,9 @@ def process_issue(*, issue: Issue, settings: Settings) -> None:
labeled_events=labeled_events, label=keyword
)
if (
last_comment
last_date
and keyword_event
and last_comment.created_at > keyword_event.created_at
and last_date > keyword_event.created_at
):
logging.info(
f"Not closing as the last comment was written after adding the "
Expand Down Expand Up @@ -141,7 +157,7 @@ def process_issue(*, issue: Issue, settings: Settings) -> None:
github_event: Optional[PartialGitHubEvent] = None
if settings.github_event_path.is_file():
contents = settings.github_event_path.read_text()
github_event = PartialGitHubEvent.parse_raw(contents)
github_event = PartialGitHubEvent.model_validate_json(contents)
if (
settings.github_event_name == "issues"
or settings.github_event_name == "pull_request_target"
Expand Down