Skip to content

Commit 2b04cf5

Browse files
authored
feat(alerts): Add notification_uuid to sent analytics (#55937)
fixes #54422
1 parent a5bcb71 commit 2b04cf5

File tree

11 files changed

+81
-6
lines changed

11 files changed

+81
-6
lines changed

src/sentry/integrations/discord/actions/notification.py

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def send_notification(event: GroupEvent, futures: Sequence[RuleFuture]) -> None:
7373
organization_id=event.organization.id,
7474
project_id=event.project_id,
7575
group_id=event.group_id,
76+
notification_uuid=notification_uuid if notification_uuid else "",
7677
)
7778
metrics.incr("notifications.sent", instance="discord.notifications", skip_internal=False)
7879
yield self.future(send_notification, key=key)

src/sentry/integrations/discord/analytics.py

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ class DiscordIntegrationNotificationSent(analytics.Event):
88
analytics.Attribute("organization_id"),
99
analytics.Attribute("project_id"),
1010
analytics.Attribute("group_id"),
11+
analytics.Attribute("notification_uuid"),
12+
analytics.Attribute("alert_id", required=False),
1113
)
1214

1315

src/sentry/integrations/msteams/analytics.py

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class MSTeamsIntegrationNotificationSent(analytics.Event):
1010
analytics.Attribute("category"),
1111
analytics.Attribute("actor_id"),
1212
analytics.Attribute("user_id", required=False),
13+
analytics.Attribute("notification_uuid"),
14+
analytics.Attribute("alert_id", required=False),
1315
)
1416

1517

src/sentry/integrations/slack/analytics.py

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class SlackIntegrationNotificationSent(analytics.Event):
2929
analytics.Attribute("actor_id"),
3030
analytics.Attribute("user_id", required=False),
3131
analytics.Attribute("group_id", required=False),
32+
analytics.Attribute("notification_uuid"),
33+
analytics.Attribute("alert_id", required=False),
3234
)
3335

3436

src/sentry/mail/analytics.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ class EmailNotificationSent(analytics.Event):
1414
analytics.Attribute("group_id", required=False),
1515
analytics.Attribute("id"),
1616
analytics.Attribute("actor_type"),
17-
# Remove after IssueAlertFallbackExperiment
18-
analytics.Attribute("fallback_experiment", required=False),
17+
analytics.Attribute("notification_uuid"),
18+
analytics.Attribute("alert_id", required=False),
1919
)
2020

2121

src/sentry/notifications/notifications/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def record_notification_sent(self, recipient: RpcActor, provider: ExternalProvid
158158
self.record_analytics(
159159
f"integrations.{provider.name}.notification_sent",
160160
category=self.metrics_key,
161+
notification_uuid=self.notification_uuid if self.notification_uuid else "",
161162
**self.get_log_params(recipient),
162163
)
163164
# record an optional second event

src/sentry/notifications/notifications/digest.py

+13
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,16 @@ def send(self) -> None:
216216
participants_to_remove.add(participant)
217217
participants -= participants_to_remove
218218
notify(provider, self, participants, shared_context, extra_context)
219+
220+
def get_log_params(self, recipient: RpcActor) -> Mapping[str, Any]:
221+
try:
222+
alert_id = list(self.digest.keys())[0].id
223+
except Exception:
224+
alert_id = None
225+
226+
return {
227+
"target_type": self.target_type.value,
228+
"target_identifier": self.target_identifier,
229+
"alert_id": alert_id,
230+
**super().get_log_params(recipient),
231+
}

src/sentry/notifications/notifications/rules.py

+1
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,6 @@ def get_log_params(self, recipient: RpcActor) -> Mapping[str, Any]:
291291
return {
292292
"target_type": self.target_type,
293293
"target_identifier": self.target_identifier,
294+
"alert_id": self.rules[0].id if self.rules else None,
294295
**super().get_log_params(recipient),
295296
}

tests/sentry/mail/test_adapter.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from functools import cached_property
55
from typing import Mapping, Sequence
66
from unittest import mock
7+
from unittest.mock import ANY
78

89
import pytz
910
from django.contrib.auth.models import AnonymousUser
@@ -170,7 +171,8 @@ def test_project_level_prefix(self):
170171

171172

172173
class MailAdapterNotifyTest(BaseMailAdapterTest):
173-
def test_simple_notification(self):
174+
@mock.patch("sentry.analytics.record")
175+
def test_simple_notification(self, mock_record):
174176
event = self.store_event(
175177
data={"message": "Hello world", "level": "error"}, project_id=self.project.id
176178
)
@@ -189,6 +191,21 @@ def test_simple_notification(self):
189191
assert isinstance(msg.alternatives[0][0], str)
190192
assert "my rule" in msg.alternatives[0][0]
191193
assert "notification_uuid" in msg.body
194+
mock_record.assert_called_with(
195+
"integrations.email.notification_sent",
196+
category="issue_alert",
197+
target_type=ANY,
198+
target_identifier=None,
199+
project_id=self.project.id,
200+
organization_id=self.organization.id,
201+
group_id=event.group_id,
202+
actor_id=ANY,
203+
user_id=ANY,
204+
id=ANY,
205+
actor_type="User",
206+
notification_uuid=ANY,
207+
alert_id=rule.id,
208+
)
192209

193210
def test_simple_snooze(self):
194211
"""Test that notification for alert snoozed by user is not send to that user."""

tests/sentry/notifications/notifications/test_digests.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import uuid
22
from unittest import mock
3-
from unittest.mock import patch
3+
from unittest.mock import ANY, patch
44
from urllib.parse import parse_qs
55

66
import responses
@@ -94,14 +94,30 @@ def setUp(self):
9494
teams=[self.team],
9595
)
9696

97-
def test_sends_digest_to_every_member(self):
97+
@patch("sentry.analytics.record")
98+
def test_sends_digest_to_every_member(self, mock_record):
9899
"""Test that each member of the project the events are created in receive a digest email notification"""
99100
event_count = 4
100101
self.run_test(event_count=event_count, performance_issues=True, generic_issues=True)
101102
assert f"{event_count + 2} new alerts since" in mail.outbox[0].subject
102103
assert "N+1 Query" in mail.outbox[0].body
103104
assert "oh no" in mail.outbox[0].body
104105
assert self.build_occurrence_data()["issue_title"] in mail.outbox[0].body
106+
mock_record.assert_called_with(
107+
"integrations.email.notification_sent",
108+
category="digest",
109+
notification_uuid=ANY,
110+
target_type="IssueOwners",
111+
target_identifier=None,
112+
alert_id=self.rule.id,
113+
project_id=self.project.id,
114+
organization_id=self.organization.id,
115+
actor_id=ANY,
116+
id=ANY,
117+
actor_type="User",
118+
group_id=None,
119+
user_id=ANY,
120+
)
105121

106122
def test_sends_alert_rule_notification_to_each_member(self):
107123
"""Test that if there is only one event it is sent as a regular alert rule notification"""

tests/sentry/notifications/test_notifications.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import uuid
33
from time import time
44
from unittest.mock import patch
5-
from urllib.parse import parse_qs
5+
from urllib.parse import parse_qs, urlparse
66

77
import responses
88
from django.conf import settings
@@ -54,6 +54,13 @@ def get_attachment():
5454
return attachments[0], data["text"][0]
5555

5656

57+
def get_notification_uuid(url: str):
58+
query_params = parse_qs(urlparse(url).query)
59+
notification_uuid = query_params["notification_uuid"][0]
60+
assert len(notification_uuid) > 1
61+
return notification_uuid
62+
63+
5764
# The analytics event `name` was called with `kwargs` being a subset of its properties
5865
def analytics_called_with_args(fn, name, **kwargs):
5966
for call_args, call_kwargs in fn.call_args_list:
@@ -216,19 +223,22 @@ def test_sends_resolution_notification(self, record_analytics):
216223
attachment["footer"]
217224
== f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=resolved_activity-slack-user|Notification Settings>"
218225
)
226+
notification_uuid = get_notification_uuid(attachment["title_link"])
219227
assert analytics_called_with_args(
220228
record_analytics,
221229
"integrations.email.notification_sent",
222230
user_id=self.user.id,
223231
organization_id=self.organization.id,
224232
group_id=self.group.id,
233+
notification_uuid=notification_uuid,
225234
)
226235
assert analytics_called_with_args(
227236
record_analytics,
228237
"integrations.slack.notification_sent",
229238
user_id=self.user.id,
230239
organization_id=self.organization.id,
231240
group_id=self.group.id,
241+
notification_uuid=notification_uuid,
232242
)
233243

234244
@responses.activate
@@ -336,19 +346,22 @@ def test_sends_regression_notification(self, record_analytics):
336346
attachment["footer"]
337347
== f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=regression_activity-slack-user|Notification Settings>"
338348
)
349+
notification_uuid = get_notification_uuid(attachment["title_link"])
339350
assert analytics_called_with_args(
340351
record_analytics,
341352
"integrations.email.notification_sent",
342353
user_id=self.user.id,
343354
organization_id=self.organization.id,
344355
group_id=group.id,
356+
notification_uuid=notification_uuid,
345357
)
346358
assert analytics_called_with_args(
347359
record_analytics,
348360
"integrations.slack.notification_sent",
349361
user_id=self.user.id,
350362
organization_id=self.organization.id,
351363
group_id=group.id,
364+
notification_uuid=notification_uuid,
352365
)
353366

354367
@responses.activate
@@ -390,19 +403,22 @@ def test_sends_resolved_in_release_notification(self, record_analytics):
390403
attachment["footer"]
391404
== f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=resolved_in_release_activity-slack-user|Notification Settings>"
392405
)
406+
notification_uuid = get_notification_uuid(attachment["title_link"])
393407
assert analytics_called_with_args(
394408
record_analytics,
395409
"integrations.email.notification_sent",
396410
user_id=self.user.id,
397411
organization_id=self.organization.id,
398412
group_id=self.group.id,
413+
notification_uuid=notification_uuid,
399414
)
400415
assert analytics_called_with_args(
401416
record_analytics,
402417
"integrations.slack.notification_sent",
403418
user_id=self.user.id,
404419
organization_id=self.organization.id,
405420
group_id=self.group.id,
421+
notification_uuid=notification_uuid,
406422
)
407423

408424
@responses.activate
@@ -468,17 +484,21 @@ def test_sends_issue_notification(self, record_analytics):
468484
attachment["footer"]
469485
== f"{self.project.slug} | <http://testserver/settings/account/notifications/alerts/?referrer=issue_alert-slack-user|Notification Settings>"
470486
)
487+
notification_uuid = get_notification_uuid(attachment["title_link"])
488+
assert len(notification_uuid) > 1
471489
assert analytics_called_with_args(
472490
record_analytics,
473491
"integrations.email.notification_sent",
474492
user_id=self.user.id,
475493
organization_id=self.organization.id,
476494
group_id=event.group_id,
495+
notification_uuid=notification_uuid,
477496
)
478497
assert analytics_called_with_args(
479498
record_analytics,
480499
"integrations.slack.notification_sent",
481500
user_id=self.user.id,
482501
organization_id=self.organization.id,
483502
group_id=event.group_id,
503+
notification_uuid=notification_uuid,
484504
)

0 commit comments

Comments
 (0)