Repository: getsentry/sentry

Issue:
Discord `alert.sent` event
start sending `alert.sent` analytic events for discord

add category to existing notification_sent event


Discussion:

Resolving patch:
diff --git a/src/sentry/integrations/discord/actions/notification.py b/src/sentry/integrations/discord/actions/notification.py
--- a/src/sentry/integrations/discord/actions/notification.py
+++ b/src/sentry/integrations/discord/actions/notification.py
@@ -1,6 +1,6 @@
 from typing import Any, Generator, Optional, Sequence
 
-from sentry import analytics, features
+from sentry import features
 from sentry.eventstore.models import GroupEvent
 from sentry.integrations.discord.actions.form import DiscordNotifyServiceForm
 from sentry.integrations.discord.client import DiscordClient
@@ -65,16 +65,11 @@ def send_notification(event: GroupEvent, futures: Sequence[RuleFuture]) -> None:
                         "channel_id": channel_id,
                     },
                 )
+            rule = rules[0] if rules else None
+            self.record_notification_sent(event, channel_id, rule, notification_uuid)
 
         key = f"discord:{integration.id}:{channel_id}"
 
-        analytics.record(
-            "integrations.discord.notification_sent",
-            organization_id=event.organization.id,
-            project_id=event.project_id,
-            group_id=event.group_id,
-            notification_uuid=notification_uuid if notification_uuid else "",
-        )
         metrics.incr("notifications.sent", instance="discord.notifications", skip_internal=False)
         yield self.future(send_notification, key=key)
 
diff --git a/src/sentry/integrations/discord/analytics.py b/src/sentry/integrations/discord/analytics.py
--- a/src/sentry/integrations/discord/analytics.py
+++ b/src/sentry/integrations/discord/analytics.py
@@ -7,6 +7,7 @@ class DiscordIntegrationNotificationSent(analytics.Event):
     attributes = (
         analytics.Attribute("organization_id"),
         analytics.Attribute("project_id"),
+        analytics.Attribute("category"),
         analytics.Attribute("group_id"),
         analytics.Attribute("notification_uuid"),
         analytics.Attribute("alert_id", required=False),
diff --git a/src/sentry/rules/actions/integrations/base.py b/src/sentry/rules/actions/integrations/base.py
--- a/src/sentry/rules/actions/integrations/base.py
+++ b/src/sentry/rules/actions/integrations/base.py
@@ -5,7 +5,9 @@
 
 from django import forms
 
-from sentry.models import OrganizationStatus
+from sentry import analytics
+from sentry.eventstore.models import GroupEvent
+from sentry.models import OrganizationStatus, Rule
 from sentry.rules.actions import EventAction
 from sentry.services.hybrid_cloud.integration import (
     RpcIntegration,
@@ -83,3 +85,31 @@ def get_organization_integration(self) -> RpcOrganizationIntegration | None:
 
     def get_form_instance(self) -> forms.Form:
         return self.form_cls(self.data, integrations=self.get_integrations())
+
+    def record_notification_sent(
+        self,
+        event: GroupEvent,
+        external_id: str,
+        rule: Rule | None = None,
+        notification_uuid: str | None = None,
+    ) -> None:
+        # Currently these actions can only be triggered by issue alerts
+        analytics.record(
+            f"integrations.{self.provider}.notification_sent",
+            category="issue_alert",
+            organization_id=event.organization.id,
+            project_id=event.project_id,
+            group_id=event.group_id,
+            notification_uuid=notification_uuid if notification_uuid else "",
+            alert_id=rule.id if rule else None,
+        )
+        analytics.record(
+            "alert.sent",
+            provider=self.provider,
+            alert_id=rule.id if rule else "",
+            alert_type="issue_alert",
+            organization_id=event.organization.id,
+            project_id=event.project_id,
+            external_id=external_id,
+            notification_uuid=notification_uuid if notification_uuid else "",
+        )


Test patch:
diff --git a/tests/sentry/integrations/discord/test_issue_alert.py b/tests/sentry/integrations/discord/test_issue_alert.py
--- a/tests/sentry/integrations/discord/test_issue_alert.py
+++ b/tests/sentry/integrations/discord/test_issue_alert.py
@@ -1,4 +1,5 @@
 from unittest import mock
+from uuid import uuid4
 
 import responses
 from django.core.exceptions import ValidationError
@@ -62,8 +63,12 @@ def setUp(self):
         )
 
     @responses.activate
-    def test_basic(self):
-        results = list(self.rule.after(self.event, self.get_state()))
+    @mock.patch("sentry.analytics.record")
+    def test_basic(self, mock_record):
+        notification_uuid = str(uuid4())
+        results = list(
+            self.rule.after(self.event, self.get_state(), notification_uuid=notification_uuid)
+        )
         assert len(results) == 1
 
         with self.feature("organizations:integrations-discord-notifications"):
@@ -82,6 +87,7 @@ def test_basic(self):
                 False,
                 None,
                 ExternalProviders.DISCORD,
+                notification_uuid=notification_uuid,
             ),
             "color": LEVEL_TO_COLOR["error"],
             "footer": {"text": build_footer(self.event.group, self.event.project, None, "{text}")},
@@ -100,6 +106,25 @@ def test_basic(self):
             buttons[2]["custom_id"]
             == f"{DiscordComponentCustomIds.ASSIGN_DIALOG}:{self.event.group.id}"
         )
+        mock_record.assert_any_call(
+            "integrations.discord.notification_sent",
+            category="issue_alert",
+            organization_id=self.organization.id,
+            project_id=self.project.id,
+            group_id=self.event.group_id,
+            notification_uuid=notification_uuid,
+            alert_id=None,
+        )
+        mock_record.assert_called_with(
+            "alert.sent",
+            provider="discord",
+            alert_id="",
+            alert_type="issue_alert",
+            organization_id=self.organization.id,
+            project_id=self.project.id,
+            external_id=self.channel_id,
+            notification_uuid=notification_uuid,
+        )
 
     @responses.activate
     def test_has_releases(self):