-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
✨ feat(github): implement issue update webhook handler #100471
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
from sentry.integrations.types import IntegrationProviderSlug | ||
from sentry.integrations.utils.metrics import IntegrationWebhookEvent, IntegrationWebhookEventType | ||
from sentry.integrations.utils.scope import clear_tags_and_context | ||
from sentry.integrations.utils.sync import sync_group_assignee_inbound_by_external_actor | ||
from sentry.models.commit import Commit | ||
from sentry.models.commitauthor import CommitAuthor | ||
from sentry.models.commitfilechange import CommitFileChange, post_bulk_create | ||
|
@@ -497,6 +498,63 @@ def _handle( | |
repo.save() | ||
|
||
|
||
class IssuesEventWebhook(GitHubWebhook): | ||
"""https://developer.github.com/v3/activity/events/types/#issuesevent""" | ||
|
||
@property | ||
def event_type(self) -> IntegrationWebhookEventType: | ||
return IntegrationWebhookEventType.INBOUND_SYNC | ||
|
||
def _handle(self, integration: RpcIntegration, event: Mapping[str, Any], **kwargs: Any) -> None: | ||
""" | ||
Handle GitHub issue events, particularly assignment and status changes. | ||
""" | ||
|
||
action = event.get("action") | ||
issue = event.get("issue", {}) | ||
repository = event.get("repository", {}) | ||
repo_full_name = repository.get("full_name") | ||
issue_number = issue.get("number") | ||
assignee_gh_name = event.get("assignee", {}).get("login") | ||
|
||
if not repo_full_name or not issue_number or not assignee_gh_name: | ||
logger.warning( | ||
"github.webhook.missing-data", | ||
extra={ | ||
"integration_id": integration.id, | ||
"repo": repo_full_name, | ||
"issue_number": issue_number, | ||
"action": action, | ||
}, | ||
) | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Webhook Fails on Null AssigneeThe |
||
|
||
external_issue_key = f"{repo_full_name}#{issue_number}" | ||
|
||
# Handle issue assignment changes | ||
if action in ["assigned", "unassigned"]: | ||
# Sentry uses the @username format for assignees | ||
assignee_name = "@" + assignee_gh_name | ||
|
||
# Sync the assignment to Sentry | ||
sync_group_assignee_inbound_by_external_actor( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to FF this? i am thinking through how we will FF all the features i am adding also per integration and can't think of a clean way to do this. open to suggestions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call! yes, let's feature flag. |
||
integration=integration, | ||
external_user_name=assignee_name, | ||
external_issue_key=external_issue_key, | ||
assign=(action == "assigned"), | ||
) | ||
|
||
logger.info( | ||
"github.webhook.assignment.synced", | ||
extra={ | ||
"integration_id": integration.id, | ||
"external_issue_key": external_issue_key, | ||
"assignee_name": assignee_name, | ||
"action": action, | ||
}, | ||
) | ||
|
||
|
||
class PullRequestEventWebhook(GitHubWebhook): | ||
"""https://developer.github.com/v3/activity/events/types/#pullrequestevent""" | ||
|
||
|
@@ -621,6 +679,7 @@ class GitHubIntegrationsWebhookEndpoint(Endpoint): | |
"push": PushEventWebhook, | ||
"pull_request": PullRequestEventWebhook, | ||
"installation": InstallationEventWebhook, | ||
"issues": IssuesEventWebhook, | ||
} | ||
|
||
def get_handler(self, event_type: str) -> type[GitHubWebhook] | None: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential bug: The code will raise an
AttributeError
when the GitHub webhook payload contains"assignee": null
, as it attempts to call.get("login")
on aNone
object.Description: The expression
event.get("assignee", {}).get("login")
will raise anAttributeError
when processing a GitHub webhook for an unassigned issue. The GitHub API sends"assignee": null
in this scenario. Theevent.get("assignee", {})
call correctly returnsNone
instead of the default{}
, but the subsequent chained call to.get("login")
on thisNone
value causes the exception. This crashes the webhook handler before it reaches the intended validation logic that checks for a falsyassignee_gh_name
, preventing the synchronization of issue unassignments from GitHub.Suggested fix: Safely access the nested
login
value. First, retrieve theassignee
object usingassignee = event.get("assignee")
. Then, conditionally get thelogin
value, for example:assignee_gh_name = assignee.get("login") if assignee else None
.severity: 0.75, confidence: 0.95
Did we get this right? 👍 / 👎 to inform future reviews.