Browse Source

feat(discord): Add Discord to models and logic.py (#57068)

Split from https://github.com/getsentry/sentry/pull/55928

The overall feature has been tested and approved.
Julia Hoge 1 year ago
parent
commit
1abd3b8120

+ 37 - 19
src/sentry/incidents/logic.py

@@ -1131,15 +1131,21 @@ def create_alert_rule_trigger_action(
         if target_type != AlertRuleTriggerAction.TargetType.SPECIFIC:
             raise InvalidTriggerActionError("Must specify specific target type")
 
-        target_identifier, target_display = get_target_identifier_display_for_integration(
-            type.value,
-            target_identifier,
-            trigger.alert_rule.organization,
-            integration_id,
-            use_async_lookup=use_async_lookup,
-            input_channel_id=input_channel_id,
-            integrations=integrations,
-        )
+        if not (
+            type.value == AlertRuleTriggerAction.Type.DISCORD
+            and not features.has(
+                "organizations:integrations-discord-metric-alerts", trigger.alert_rule.organization
+            )
+        ):
+            target_identifier, target_display = get_target_identifier_display_for_integration(
+                type.value,
+                target_identifier,
+                trigger.alert_rule.organization,
+                integration_id,
+                use_async_lookup=use_async_lookup,
+                input_channel_id=input_channel_id,
+                integrations=integrations,
+            )
     elif type == AlertRuleTriggerAction.Type.SENTRY_APP:
         target_identifier, target_display = get_alert_rule_trigger_action_sentry_app(
             trigger.alert_rule.organization, sentry_app_id, installations
@@ -1200,16 +1206,23 @@ def update_alert_rule_trigger_action(
             integration_id = updated_fields.get("integration_id", trigger_action.integration_id)
             organization = trigger_action.alert_rule_trigger.alert_rule.organization
 
-            target_identifier, target_display = get_target_identifier_display_for_integration(
-                type,
-                target_identifier,
-                organization,
-                integration_id,
-                use_async_lookup=use_async_lookup,
-                input_channel_id=input_channel_id,
-                integrations=integrations,
-            )
-            updated_fields["target_display"] = target_display
+            if not (
+                type == AlertRuleTriggerAction.Type.DISCORD
+                and not features.has(
+                    "organizations:integrations-discord-metric-alerts",
+                    organization,
+                )
+            ):
+                target_identifier, target_display = get_target_identifier_display_for_integration(
+                    type,
+                    target_identifier,
+                    organization,
+                    integration_id,
+                    use_async_lookup=use_async_lookup,
+                    input_channel_id=input_channel_id,
+                    integrations=integrations,
+                )
+                updated_fields["target_display"] = target_display
 
         elif type == AlertRuleTriggerAction.Type.SENTRY_APP.value:
             sentry_app_id = updated_fields.get("sentry_app_id", trigger_action.sentry_app_id)
@@ -1243,6 +1256,11 @@ def get_target_identifier_display_for_integration(type, target_value, *args, **k
         target_identifier = get_alert_rule_trigger_action_msteams_channel_id(
             target_value, *args, **kwargs
         )
+
+    elif type == AlertRuleTriggerAction.Type.DISCORD.value:
+        # Since we don't have a name associated with Discord channels, identifier and value are both the channel id
+        target_identifier = target_value
+
     # target_value is the ID of the PagerDuty service
     elif type == AlertRuleTriggerAction.Type.PAGERDUTY.value:
         target_identifier, target_value = get_alert_rule_trigger_action_pagerduty_service(

+ 7 - 1
src/sentry/incidents/models.py

@@ -596,7 +596,13 @@ class AlertRuleTriggerAction(AbstractNotificationAction):
     _type_registrations = {}
 
     INTEGRATION_TYPES = frozenset(
-        (Type.PAGERDUTY.value, Type.SLACK.value, Type.MSTEAMS.value, Type.OPSGENIE.value)
+        (
+            Type.PAGERDUTY.value,
+            Type.SLACK.value,
+            Type.MSTEAMS.value,
+            Type.OPSGENIE.value,
+            Type.DISCORD.value,
+        )
     )
 
     # ActionService items which are not supported for AlertRuleTriggerActions

+ 3 - 0
src/sentry/models/notificationaction.py

@@ -51,6 +51,7 @@ class ActionService(FlexibleIntEnum):
     SENTRY_APP = 4
     SENTRY_NOTIFICATION = 5  # Use personal notification platform (src/sentry/notifications)
     OPSGENIE = 6
+    DISCORD = 7
 
     @classmethod
     def as_choices(cls) -> tuple[tuple[int, str], ...]:
@@ -59,6 +60,7 @@ class ActionService(FlexibleIntEnum):
         assert ExternalProviders.SLACK.name is not None
         assert ExternalProviders.MSTEAMS.name is not None
         assert ExternalProviders.OPSGENIE.name is not None
+        assert ExternalProviders.DISCORD.name is not None
         return (
             (cls.EMAIL.value, ExternalProviders.EMAIL.name),
             (cls.PAGERDUTY.value, ExternalProviders.PAGERDUTY.name),
@@ -67,6 +69,7 @@ class ActionService(FlexibleIntEnum):
             (cls.SENTRY_APP.value, "sentry_app"),
             (cls.SENTRY_NOTIFICATION.value, "sentry_notification"),
             (cls.OPSGENIE.value, ExternalProviders.OPSGENIE.name),
+            (cls.DISCORD.value, ExternalProviders.DISCORD.name),
         )
 
 

+ 84 - 0
tests/sentry/incidents/test_logic.py

@@ -1425,6 +1425,46 @@ class CreateAlertRuleTriggerActionTest(BaseAlertRuleTriggerActionTest, TestCase)
                 integration_id=integration.id,
             )
 
+    @responses.activate
+    def test_discord(self):
+        base_url: str = "https://discord.com/api/v10"
+        channel_id = "channel-id"
+        guild_id = "example-discord-server"
+        guild_name = "Server Name"
+        integration = Integration.objects.create(
+            provider="discord",
+            name="Example Discord",
+            external_id=f"{guild_id}",
+            metadata={
+                "guild_id": f"{guild_id}",
+                "name": f"{guild_name}",
+            },
+        )
+        type = AlertRuleTriggerAction.Type.DISCORD
+        target_type = AlertRuleTriggerAction.TargetType.SPECIFIC
+        responses.add(
+            method=responses.GET,
+            url=f"{base_url}/channels/{channel_id}",
+            json={
+                "guild_id": f"{guild_id}",
+                "name": f"{guild_name}",
+            },
+        )
+        with self.feature("organizations:integrations-discord-metric-alerts"):
+            action = create_alert_rule_trigger_action(
+                self.trigger,
+                type,
+                target_type,
+                target_identifier=channel_id,
+                integration_id=integration.id,
+            )
+        assert action.alert_rule_trigger == self.trigger
+        assert action.type == type.value
+        assert action.target_type == target_type.value
+        assert action.target_identifier == channel_id
+        assert action.target_display == channel_id
+        assert action.integration_id == integration.id
+
 
 class UpdateAlertRuleTriggerAction(BaseAlertRuleTriggerActionTest, TestCase):
     @cached_property
@@ -1723,6 +1763,50 @@ class UpdateAlertRuleTriggerAction(BaseAlertRuleTriggerActionTest, TestCase):
                 integration_id=integration.id,
             )
 
+    @responses.activate
+    def test_discord(self):
+        base_url: str = "https://discord.com/api/v10"
+        channel_id = "channel-id"
+        guild_id = "example-discord-server"
+        guild_name = "Server Name"
+
+        integration = Integration.objects.create(
+            provider="discord",
+            name="Example Discord",
+            external_id=f"{guild_id}",
+            metadata={
+                "guild_id": f"{guild_id}",
+                "name": f"{guild_name}",
+            },
+        )
+
+        integration.add_organization(self.organization, self.user)
+        type = AlertRuleTriggerAction.Type.DISCORD
+        target_type = AlertRuleTriggerAction.TargetType.SPECIFIC
+        responses.add(
+            method=responses.GET,
+            url=f"{base_url}/channels/{channel_id}",
+            json={
+                "guild_id": f"{guild_id}",
+                "name": f"{guild_name}",
+            },
+        )
+
+        with self.feature("organizations:integrations-discord-metric-alerts"):
+            action = update_alert_rule_trigger_action(
+                self.action,
+                type,
+                target_type,
+                target_identifier=channel_id,
+                integration_id=integration.id,
+            )
+        assert action.alert_rule_trigger == self.trigger
+        assert action.type == type.value
+        assert action.target_type == target_type.value
+        assert action.target_identifier == channel_id
+        assert action.target_display == channel_id
+        assert action.integration_id == integration.id
+
 
 class DeleteAlertRuleTriggerAction(BaseAlertRuleTriggerActionTest, TestCase):
     @cached_property