Skip to content

Commit 8ee6a25

Browse files
chore(sentry apps): Add context manager for comment webhook SLOs (#86739)
1 parent b30d05e commit 8ee6a25

File tree

2 files changed

+65
-41
lines changed

2 files changed

+65
-41
lines changed

src/sentry/sentry_apps/tasks/sentry_apps.py

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
retry_decorator = retry(
7272
on=(RequestException, ApiHostError, ApiTimeoutError),
7373
ignore=(ClientError,),
74-
ignore_and_capture=(SentryAppSentryError, AssertionError),
74+
ignore_and_capture=(SentryAppSentryError, AssertionError, ValueError),
7575
)
7676

7777
# We call some models by a different name, publicly, than their class name.
@@ -227,15 +227,10 @@ def _process_resource_change(
227227
# Looks up the human name for the model. Defaults to the model name.
228228
name = RESOURCE_RENAMES.get(model.__name__, model.__name__.lower())
229229

230-
try:
231-
event = SentryAppEventType(f"{name}.{action}")
232-
except ValueError as e:
233-
raise SentryAppSentryError(
234-
message=f"{SentryAppWebhookFailureReason.INVALID_EVENT}",
235-
) from e
236-
230+
event = SentryAppEventType(f"{name}.{action}")
237231
with SentryAppInteractionEvent(
238-
operation_type=SentryAppInteractionType.PREPARE_WEBHOOK, event_type=event
232+
operation_type=SentryAppInteractionType.PREPARE_WEBHOOK,
233+
event_type=event,
239234
).capture():
240235
project_id: int | None = kwargs.get("project_id", None)
241236
group_id: int | None = kwargs.get("group_id", None)
@@ -386,11 +381,7 @@ def clear_region_cache(sentry_app_id: int, region_name: str) -> None:
386381
def workflow_notification(
387382
installation_id: int, issue_id: int, type: str, user_id: int | None, *args: Any, **kwargs: Any
388383
) -> None:
389-
try:
390-
event = SentryAppEventType(f"issue.{type}")
391-
except ValueError as e:
392-
raise SentryAppSentryError(message=SentryAppWebhookFailureReason.INVALID_EVENT) from e
393-
384+
event = SentryAppEventType(f"issue.{type}")
394385
with SentryAppInteractionEvent(
395386
operation_type=SentryAppInteractionType.PREPARE_WEBHOOK,
396387
event_type=event,
@@ -417,22 +408,28 @@ def workflow_notification(
417408
def build_comment_webhook(
418409
installation_id: int, issue_id: int, type: str, user_id: int, *args: Any, **kwargs: Any
419410
) -> None:
420-
webhook_data = get_webhook_data(installation_id, issue_id, user_id)
421-
install, _, user = webhook_data
422-
data = kwargs.get("data", {})
423-
project_slug = data.get("project_slug")
424-
comment_id = data.get("comment_id")
425-
payload = {
426-
"comment_id": data.get("comment_id"),
427-
"issue_id": issue_id,
428-
"project_slug": data.get("project_slug"),
429-
"timestamp": data.get("timestamp"),
430-
"comment": data.get("comment"),
431-
}
432-
send_webhooks(installation=install, event=type, data=payload, actor=user)
433-
# `type` is comment.created, comment.updated, or comment.deleted
411+
event = SentryAppEventType(type)
412+
with SentryAppInteractionEvent(
413+
operation_type=SentryAppInteractionType.PREPARE_WEBHOOK,
414+
event_type=event,
415+
).capture():
416+
webhook_data = get_webhook_data(installation_id, issue_id, user_id)
417+
install, _, user = webhook_data
418+
data = kwargs.get("data", {})
419+
project_slug = data.get("project_slug")
420+
comment_id = data.get("comment_id")
421+
payload = {
422+
"comment_id": data.get("comment_id"),
423+
"issue_id": issue_id,
424+
"project_slug": data.get("project_slug"),
425+
"timestamp": data.get("timestamp"),
426+
"comment": data.get("comment"),
427+
}
428+
429+
send_webhooks(installation=install, event=event, data=payload, actor=user)
430+
# `event` is comment.created, comment.updated, or comment.deleted
434431
analytics.record(
435-
type,
432+
event,
436433
user_id=user_id,
437434
group_id=issue_id,
438435
project_slug=project_slug,
@@ -526,15 +523,9 @@ def notify_sentry_app(event: GroupEvent, futures: Sequence[RuleFuture]):
526523

527524

528525
def send_webhooks(installation: RpcSentryAppInstallation, event: str, **kwargs: Any) -> None:
529-
try:
530-
event = SentryAppEventType(event)
531-
except ValueError as e:
532-
raise SentryAppSentryError(
533-
message=f"{SentryAppWebhookFailureReason.INVALID_EVENT}",
534-
) from e
535-
536526
with SentryAppInteractionEvent(
537-
operation_type=SentryAppInteractionType.SEND_WEBHOOK, event_type=event
527+
operation_type=SentryAppInteractionType.SEND_WEBHOOK,
528+
event_type=SentryAppEventType(event),
538529
).capture():
539530
servicehook: ServiceHook
540531
try:

tests/sentry/sentry_apps/tasks/test_sentry_apps.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,8 @@ def setUp(self):
10121012
"project_slug": self.note.project.slug,
10131013
}
10141014

1015-
def test_sends_comment_created_webhook(self, safe_urlopen):
1015+
@patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
1016+
def test_sends_comment_created_webhook(self, mock_record, safe_urlopen):
10161017
build_comment_webhook(
10171018
self.install.id, self.issue.id, "comment.created", self.user.id, data=self.data
10181019
)
@@ -1024,7 +1025,18 @@ def test_sends_comment_created_webhook(self, safe_urlopen):
10241025
assert data["action"] == "created"
10251026
assert data["data"]["issue_id"] == self.issue.id
10261027

1027-
def test_sends_comment_updated_webhook(self, safe_urlopen):
1028+
# SLO assertions
1029+
assert_success_metric(mock_record)
1030+
# PREPARE_WEBHOOK (success) -> SEND_WEBHOOK (success) -> SEND_WEBHOOK (success)
1031+
assert_count_of_metric(
1032+
mock_record=mock_record, outcome=EventLifecycleOutcome.STARTED, outcome_count=3
1033+
)
1034+
assert_count_of_metric(
1035+
mock_record=mock_record, outcome=EventLifecycleOutcome.SUCCESS, outcome_count=3
1036+
)
1037+
1038+
@patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
1039+
def test_sends_comment_updated_webhook(self, mock_record, safe_urlopen):
10281040
self.data.update(data={"text": "goodbye world"})
10291041
build_comment_webhook(
10301042
self.install.id, self.issue.id, "comment.updated", self.user.id, data=self.data
@@ -1037,7 +1049,18 @@ def test_sends_comment_updated_webhook(self, safe_urlopen):
10371049
assert data["action"] == "updated"
10381050
assert data["data"]["issue_id"] == self.issue.id
10391051

1040-
def test_sends_comment_deleted_webhook(self, safe_urlopen):
1052+
# SLO assertions
1053+
assert_success_metric(mock_record)
1054+
# PREPARE_WEBHOOK (success) -> SEND_WEBHOOK (success) -> SEND_WEBHOOK (success)
1055+
assert_count_of_metric(
1056+
mock_record=mock_record, outcome=EventLifecycleOutcome.STARTED, outcome_count=3
1057+
)
1058+
assert_count_of_metric(
1059+
mock_record=mock_record, outcome=EventLifecycleOutcome.SUCCESS, outcome_count=3
1060+
)
1061+
1062+
@patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
1063+
def test_sends_comment_deleted_webhook(self, mock_record, safe_urlopen):
10411064
self.note.delete()
10421065
build_comment_webhook(
10431066
self.install.id, self.issue.id, "comment.deleted", self.user.id, data=self.data
@@ -1050,6 +1073,16 @@ def test_sends_comment_deleted_webhook(self, safe_urlopen):
10501073
assert data["action"] == "deleted"
10511074
assert data["data"]["issue_id"] == self.issue.id
10521075

1076+
# SLO assertions
1077+
assert_success_metric(mock_record)
1078+
# PREPARE_WEBHOOK (success) -> SEND_WEBHOOK (success) -> SEND_WEBHOOK (success)
1079+
assert_count_of_metric(
1080+
mock_record=mock_record, outcome=EventLifecycleOutcome.STARTED, outcome_count=3
1081+
)
1082+
assert_count_of_metric(
1083+
mock_record=mock_record, outcome=EventLifecycleOutcome.SUCCESS, outcome_count=3
1084+
)
1085+
10531086

10541087
@patch("sentry.utils.sentry_apps.webhooks.safe_urlopen", return_value=MockResponseInstance)
10551088
class TestWorkflowNotification(TestCase):
@@ -1367,7 +1400,7 @@ def test_ignore_issue_alert(self, mock_record, safe_urlopen):
13671400
self.sentry_app.update(status=SentryAppStatus.INTERNAL)
13681401
data = {"issue": serialize(self.issue)}
13691402
# event.alert is not in the servicehook events
1370-
with pytest.raises(SentryAppSentryError):
1403+
with pytest.raises(ValueError):
13711404
for i in range(3):
13721405
send_webhooks(
13731406
installation=self.install, event="event.alert", data=data, actor=self.user

0 commit comments

Comments
 (0)