Skip to content

Add support for many more entity events #234

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
108 changes: 92 additions & 16 deletions app/components/github_integration/comments/fetching.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import TYPE_CHECKING, cast

from githubkit.exception import RequestFailed
from githubkit.versions.latest.models import IssuePropPullRequest
from zig_codeblocks import extract_codeblocks

from app.components.github_integration.comments.discussions import (
Expand All @@ -14,13 +15,18 @@
from app.components.github_integration.mentions.cache import entity_cache
from app.components.github_integration.models import Comment, EntityGist, GitHubUser
from app.setup import gh
from app.utils import TTRCache
from app.utils import TTRCache, escape_special

if TYPE_CHECKING:
import datetime as dt
from collections.abc import AsyncIterator
from collections.abc import AsyncIterator, Callable

from githubkit.versions.latest.models import PullRequestReviewComment
from githubkit.versions.latest.models import (
Issue,
IssueEvent,
IssueEventRename,
PullRequestReviewComment,
)
from pydantic import BaseModel

COMMENT_PATTERN = re.compile(
Expand All @@ -36,14 +42,79 @@
"CHANGES_REQUESTED": 0xE74C3C, # red
}
EVENT_COLOR = 0x3498DB # blue
ENTITY_UPDATE_EVENTS = frozenset({"closed", "locked", "merged", "reopened", "unlocked"})
SUPPORTED_EVENTS = {
"assigned": "Assigned `{event.assignee.login}`",
"labeled": "Added the `{event.label.name}` label",
"milestoned": "Added this to the `{event.milestone.title}` milestone",
"review_requested": "Requested review from `{reviewer}`",
"unassigned": "Unassigned `{event.assignee.login}`",
"unlabeled": "Removed the `{event.label.name}` label",
ENTITY_UPDATE_EVENTS = frozenset(
{"closed", "locked", "merged", "reopened", "unlocked", "pinned", "unpinned"}
)
SUPPORTED_EVENTS: dict[str, str | Callable[[IssueEvent], str]] = {
"assigned": "Assigned `{event.assignee.login}`.",
"unassigned": "Unassigned `{event.assignee.login}`.",
"labeled": "Added the `{event.label.name}` label.",
"unlabeled": "Removed the `{event.label.name}` label.",
"issue_type_added": "Added an issue type.",
"issue_type_changed": "Changed the issue type.",
"issue_type_removed": "Removed the issue type.",
"milestoned": "Added this to the `{event.milestone.title}` milestone.",
"demilestoned": "Removed this from the `{event.milestone.title}` milestone.",
"convert_to_draft": "Marked this pull request as a draft.",
"ready_for_review": "Marked this pull request as ready for review.",
"review_requested": "Requested review from `{reviewer}`.",
"auto_merge_enabled": "Enabled auto-merge.",
"auto_merge_disabled": "Disabled auto-merge.",
"head_ref_deleted": "Deleted the head branch.",
"head_ref_restored": "Restored the head branch.",
"head_ref_force_pushed": lambda event: (
# HACK: there does not seem to be any easy way to get the HTML URL of the
# repository.
f"Force-pushed the head branch to [`{cast('str', event.commit_id)[:7]}`](<{
cast('Issue', event.issue)
.repository_url.replace('//api.', '//')
.replace('/repos/', '/')
}/commit/{event.commit_id}>)."
),
"base_ref_changed": "Changed the base branch.",
"automatic_base_change_failed": "Automatic base change failed.",
"automatic_base_change_succeeded": "Base automatically changed.",
"converted_to_discussion": "Converted this issue to a discussion.",
"parent_issue_added": "Added a parent issue.",
"sub_issue_added": "Added a sub-issue.",
"marked_as_duplicate": "Marked an issue as a duplicate of this one.",
"referenced": lambda event: (
f"Referenced this issue in commit [`{cast('str', event.commit_id)[:7]}`]"
# HACK: once again, there does not seem to be any other way. And for
# some reason the HTML URL requires `commit` while the API URL requires
# `commits` (note the `s`)...
f"(<{
cast('str', event.commit_url)
.replace('//api.', '//')
.replace('/repos/', '/')
.replace('commits', 'commit')
}>)."
),
"renamed": lambda event: (
f"Changed the title ~~{
escape_special((rename := cast('IssueEventRename', event.rename)).from_)
}~~ {escape_special(rename.to)}."
),
"added_to_merge_queue": "Added this pull request to the merge queue.",
"deployed": lambda event: (
f"Deployed this{
f' via {event.performed_via_github_app.name}'
if event.performed_via_github_app is not None
else ''
}."
),
"connected": lambda event: (
"Linked an issue that may be closed by this pull request."
if isinstance(cast("Issue", event.issue).pull_request, IssuePropPullRequest)
else "Linked a pull request that may close this issue."
),
"disconnected": lambda event: (
f"Removed a link to {
'a pull request'
if isinstance(cast('Issue', event.issue).pull_request, IssuePropPullRequest)
else 'an issue'
}."
),
}


Expand Down Expand Up @@ -160,7 +231,7 @@ async def _get_event(entity_gist: EntityGist, comment_id: int) -> Comment:
owner, repo, entity_no = entity_gist
event = (await gh.rest.issues.async_get_event(owner, repo, comment_id)).parsed_data
if event.event not in SUPPORTED_EVENTS.keys() | ENTITY_UPDATE_EVENTS:
body = f":ghost: Unsupported event: `{event.event}`"
body = f":ghost: Unsupported event: `{event.event}`."
elif event.event == "review_requested":
# Special-cased to handle requests for both users and teams
if event.requested_reviewer:
Expand All @@ -170,14 +241,19 @@ async def _get_event(entity_gist: EntityGist, comment_id: int) -> Comment:
# Throwing in the org name to make it clear that it's a team
org_name = event.requested_team.html_url.split("/", 5)[4]
reviewer = f"{org_name}/{event.requested_team.name}"
body = SUPPORTED_EVENTS[event.event].format(reviewer=reviewer)
formatter = SUPPORTED_EVENTS[event.event]
assert not callable(formatter)
body = formatter.format(reviewer=reviewer)
elif event.event in ENTITY_UPDATE_EVENTS:
entity = await entity_cache.get(entity_gist)
body = f"{event.event.capitalize()} the {entity.kind}"
body = f"{event.event.capitalize()} the {entity.kind.lower()}."
if event.lock_reason:
body += f"\nReason: `{event.lock_reason or 'unspecified'}`"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The right side of the or seems to be unreachable as far as I can tell, so I removed it; I may be wrong, however.

body += f"\nReason: `{event.lock_reason}`."
else:
body = SUPPORTED_EVENTS[event.event].format(event=event)
formatter = SUPPORTED_EVENTS[event.event]
body = (
formatter(event) if callable(formatter) else formatter.format(event=event)
)
# The API doesn't return an html_url, gotta construct it manually.
# It's fine to say "issues" here, GitHub will resolve the correct type
url = f"https://github.com/{owner}/{repo}/issues/{entity_no}#event-{comment_id}"
Expand Down