Repository: getsentry/sentry

Issue:
Create endpoint to return data about whether an alert is "bad"
We need an endpoint to enable a disabled alert that checks to see if the alert is no longer “bad”. If it is “bad”, show the user the error.


Discussion:

Resolving patch:
diff --git a/src/sentry/api/endpoints/project_rule_enable.py b/src/sentry/api/endpoints/project_rule_enable.py
new file mode 100644
--- /dev/null
+++ b/src/sentry/api/endpoints/project_rule_enable.py
@@ -0,0 +1,60 @@
+from rest_framework import status
+from rest_framework.request import Request
+from rest_framework.response import Response
+
+from sentry import audit_log
+from sentry.api.api_owners import ApiOwner
+from sentry.api.base import region_silo_endpoint
+from sentry.api.bases.project import ProjectAlertRulePermission, ProjectEndpoint
+from sentry.api.endpoints.project_rules import find_duplicate_rule
+from sentry.api.exceptions import ResourceDoesNotExist
+from sentry.constants import ObjectStatus
+from sentry.models import Rule
+
+
+@region_silo_endpoint
+class ProjectRuleEnableEndpoint(ProjectEndpoint):
+    owner = ApiOwner.ISSUES
+    permission_classes = (ProjectAlertRulePermission,)
+
+    def put(self, request: Request, project, rule_id) -> Response:
+        try:
+            rule = Rule.objects.get(id=rule_id, project=project)
+        except Rule.DoesNotExist:
+            raise ResourceDoesNotExist
+
+        if rule.status != ObjectStatus.DISABLED:
+            return Response(
+                {
+                    "detail": "Rule is not disabled.",
+                },
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+
+        if not rule.data.get("actions", []):
+            return Response(
+                {
+                    "detail": "Cannot enable a rule with no action.",
+                },
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+
+        duplicate_rule = find_duplicate_rule(rule.data, project)
+        if duplicate_rule:
+            return Response(
+                {
+                    "detail": f"This rule is an exact duplicate of '{duplicate_rule.label}' in this project and may not be enabled unless it's edited."
+                },
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+
+        rule.status = ObjectStatus.ACTIVE
+        rule.save()
+        self.create_audit_entry(
+            request=request,
+            organization=project.organization,
+            target_object=rule.id,
+            event=audit_log.get_event_id("RULE_EDIT"),
+            data=rule.get_audit_log_data(),
+        )
+        return Response(status=202)
diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py
--- a/src/sentry/api/urls.py
+++ b/src/sentry/api/urls.py
@@ -477,6 +477,7 @@
 from .endpoints.project_reprocessing import ProjectReprocessingEndpoint
 from .endpoints.project_rule_actions import ProjectRuleActionsEndpoint
 from .endpoints.project_rule_details import ProjectRuleDetailsEndpoint
+from .endpoints.project_rule_enable import ProjectRuleEnableEndpoint
 from .endpoints.project_rule_preview import ProjectRulePreviewEndpoint
 from .endpoints.project_rule_task_details import ProjectRuleTaskDetailsEndpoint
 from .endpoints.project_rules import ProjectRulesEndpoint
@@ -2191,6 +2192,11 @@
         ProjectRuleDetailsEndpoint.as_view(),
         name="sentry-api-0-project-rule-details",
     ),
+    re_path(
+        r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/rules/(?P<rule_id>[^\/]+)/enable/$",
+        ProjectRuleEnableEndpoint.as_view(),
+        name="sentry-api-0-project-rule-enable",
+    ),
     re_path(
         r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/rules/(?P<rule_id>[^\/]+)/snooze/$",
         RuleSnoozeEndpoint.as_view(),


Test patch:
diff --git a/tests/sentry/api/endpoints/test_project_rule_enable.py b/tests/sentry/api/endpoints/test_project_rule_enable.py
new file mode 100644
--- /dev/null
+++ b/tests/sentry/api/endpoints/test_project_rule_enable.py
@@ -0,0 +1,105 @@
+from rest_framework import status
+
+from sentry import audit_log
+from sentry.constants import ObjectStatus
+from sentry.models import AuditLogEntry, Rule
+from sentry.silo import SiloMode
+from sentry.testutils.cases import APITestCase
+from sentry.testutils.outbox import outbox_runner
+from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
+
+
+@region_silo_test(stable=True)
+class ProjectRuleEnableTestCase(APITestCase):
+    endpoint = "sentry-api-0-project-rule-enable"
+    method = "PUT"
+
+    def setUp(self):
+        self.rule = self.create_project_rule(project=self.project)
+        self.login_as(user=self.user)
+
+    def test_simple(self):
+        self.rule.status = ObjectStatus.DISABLED
+        self.rule.save()
+        with outbox_runner():
+            self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                self.rule.id,
+                status_code=status.HTTP_202_ACCEPTED,
+            )
+        assert Rule.objects.filter(id=self.rule.id, status=ObjectStatus.ACTIVE).exists()
+        with assume_test_silo_mode(SiloMode.CONTROL):
+            assert AuditLogEntry.objects.filter(
+                organization_id=self.organization.id,
+                target_object=self.rule.id,
+                event=audit_log.get_event_id("RULE_EDIT"),
+            ).exists()
+
+    def test_rule_enabled(self):
+        """Test that we do not accept an enabled rule"""
+        response = self.get_error_response(
+            self.organization.slug,
+            self.project.slug,
+            self.rule.id,
+            status_code=status.HTTP_400_BAD_REQUEST,
+        )
+        assert response.data["detail"] == "Rule is not disabled."
+
+    def test_duplicate_rule(self):
+        """Test that we do not allow enabling a rule that is an exact duplicate of another rule in the same project"""
+        conditions = [
+            {
+                "id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
+            }
+        ]
+        actions = [
+            {
+                "targetType": "IssueOwners",
+                "fallthroughType": "ActiveMembers",
+                "id": "sentry.mail.actions.NotifyEmailAction",
+                "targetIdentifier": "",
+            }
+        ]
+        rule = self.create_project_rule(
+            project=self.project, action_match=actions, condition_match=conditions
+        )
+
+        rule2 = self.create_project_rule(
+            project=self.project, action_match=actions, condition_match=conditions
+        )
+        rule2.status = ObjectStatus.DISABLED
+        rule2.save()
+
+        response = self.get_error_response(
+            self.organization.slug,
+            self.project.slug,
+            rule2.id,
+            status_code=status.HTTP_400_BAD_REQUEST,
+        )
+        assert (
+            response.data["detail"]
+            == f"This rule is an exact duplicate of '{rule.label}' in this project and may not be enabled unless it's edited."
+        )
+
+    def test_no_action_rule(self):
+        """Test that we do not allow enabling a rule that has no action(s)"""
+        conditions = [
+            {
+                "id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
+            }
+        ]
+        rule = Rule.objects.create(
+            project=self.project,
+            data={"conditions": conditions, "action_match": "all"},
+        )
+        rule.status = ObjectStatus.DISABLED
+        rule.save()
+
+        response = self.get_error_response(
+            self.organization.slug,
+            self.project.slug,
+            rule.id,
+            status_code=status.HTTP_400_BAD_REQUEST,
+        )
+        assert response.data["detail"] == "Cannot enable a rule with no action."