diff --git a/README.md b/README.md index f7164bb..e13c94c 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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: @@ -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 diff --git a/app/main.py b/app/main.py index 4a5ea63..300b5ca 100644 --- a/app/main.py +++ b/app/main.py @@ -1,11 +1,10 @@ -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 @@ -13,7 +12,9 @@ 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 @@ -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]: @@ -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: @@ -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 " @@ -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"