We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Prevent duplicate and no action alerts from being created
Alerts with no actions are useless as are alerts that are exact duplicates of another rule in the same project, so we should not allow them to be saved.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/api/endpoints/project_rule_details.py`
Content:
```
1 from __future__ import annotations
2 
3 from rest_framework import status
4 from rest_framework.request import Request
5 from rest_framework.response import Response
6 
7 from sentry import audit_log
8 from sentry.api.base import region_silo_endpoint
9 from sentry.api.bases.rule import RuleEndpoint
10 from sentry.api.serializers import serialize
11 from sentry.api.serializers.models.rule import RuleSerializer
12 from sentry.api.serializers.rest_framework.rule import RuleSerializer as DrfRuleSerializer
13 from sentry.constants import ObjectStatus
14 from sentry.integrations.slack.utils import RedisRuleStatus
15 from sentry.mediators import project_rules
16 from sentry.models import (
17     RegionScheduledDeletion,
18     RuleActivity,
19     RuleActivityType,
20     SentryAppComponent,
21     Team,
22     User,
23 )
24 from sentry.models.integrations.sentry_app_installation import (
25     SentryAppInstallation,
26     prepare_ui_component,
27 )
28 from sentry.rules.actions import trigger_sentry_app_action_creators_for_issues
29 from sentry.signals import alert_rule_edited
30 from sentry.tasks.integrations.slack import find_channel_id_for_rule
31 from sentry.web.decorators import transaction_start
32 
33 
34 @region_silo_endpoint
35 class ProjectRuleDetailsEndpoint(RuleEndpoint):
36     @transaction_start("ProjectRuleDetailsEndpoint")
37     def get(self, request: Request, project, rule) -> Response:
38         """
39         Retrieve a rule
40 
41         Return details on an individual rule.
42 
43             {method} {path}
44 
45         """
46 
47         # Serialize Rule object
48         serialized_rule = serialize(
49             rule, request.user, RuleSerializer(request.GET.getlist("expand", []))
50         )
51 
52         errors = []
53         # Prepare Rule Actions that are SentryApp components using the meta fields
54         for action in serialized_rule.get("actions", []):
55             if action.get("_sentry_app_installation") and action.get("_sentry_app_component"):
56                 installation = SentryAppInstallation(**action.get("_sentry_app_installation", {}))
57                 component = prepare_ui_component(
58                     installation,
59                     SentryAppComponent(**action.get("_sentry_app_component")),
60                     project.slug,
61                     action.get("settings"),
62                 )
63 
64                 if component is None:
65                     errors.append(
66                         {"detail": f"Could not fetch details from {installation.sentry_app.name}"}
67                     )
68                     action["disabled"] = True
69                     continue
70 
71                 action["formFields"] = component.schema.get("settings", {})
72 
73                 # Delete meta fields
74                 del action["_sentry_app_installation"]
75                 del action["_sentry_app_component"]
76 
77             # TODO(nisanthan): This is a temporary fix. We need to save both the label and value of the selected choice and not save all the choices.
78             if action.get("id") == "sentry.integrations.jira.notify_action.JiraCreateTicketAction":
79                 for field in action.get("dynamic_form_fields", []):
80                     if field.get("choices"):
81                         field["choices"] = [
82                             p
83                             for p in field.get("choices", [])
84                             if isinstance(p[0], str) and isinstance(p[1], str)
85                         ]
86 
87         if len(errors):
88             serialized_rule["errors"] = errors
89 
90         return Response(serialized_rule)
91 
92     @transaction_start("ProjectRuleDetailsEndpoint")
93     def put(self, request: Request, project, rule) -> Response:
94         """
95         Update a rule
96 
97         Update various attributes for the given rule.
98 
99             {method} {path}
100             {{
101               "name": "My rule name",
102               "conditions": [],
103               "filters": [],
104               "actions": [],
105               "actionMatch": "all",
106               "filterMatch": "all"
107             }}
108 
109         """
110         serializer = DrfRuleSerializer(
111             context={"project": project, "organization": project.organization},
112             data=request.data,
113             partial=True,
114         )
115 
116         if serializer.is_valid():
117             data = serializer.validated_data
118 
119             # combine filters and conditions into one conditions criteria for the rule object
120             conditions = data.get("conditions", [])
121             if "filters" in data:
122                 conditions.extend(data["filters"])
123 
124             kwargs = {
125                 "name": data["name"],
126                 "environment": data.get("environment"),
127                 "project": project,
128                 "action_match": data["actionMatch"],
129                 "filter_match": data.get("filterMatch"),
130                 "conditions": conditions,
131                 "actions": data["actions"],
132                 "frequency": data.get("frequency"),
133             }
134             owner = data.get("owner")
135             if owner:
136                 try:
137                     kwargs["owner"] = owner.resolve_to_actor().id
138                 except (User.DoesNotExist, Team.DoesNotExist):
139                     return Response(
140                         "Could not resolve owner",
141                         status=status.HTTP_400_BAD_REQUEST,
142                     )
143 
144             if data.get("pending_save"):
145                 client = RedisRuleStatus()
146                 kwargs.update({"uuid": client.uuid, "rule_id": rule.id})
147                 find_channel_id_for_rule.apply_async(kwargs=kwargs)
148 
149                 context = {"uuid": client.uuid}
150                 return Response(context, status=202)
151 
152             trigger_sentry_app_action_creators_for_issues(actions=kwargs.get("actions"))
153 
154             updated_rule = project_rules.Updater.run(rule=rule, request=request, **kwargs)
155 
156             RuleActivity.objects.create(
157                 rule=updated_rule, user_id=request.user.id, type=RuleActivityType.UPDATED.value
158             )
159             self.create_audit_entry(
160                 request=request,
161                 organization=project.organization,
162                 target_object=updated_rule.id,
163                 event=audit_log.get_event_id("RULE_EDIT"),
164                 data=updated_rule.get_audit_log_data(),
165             )
166             alert_rule_edited.send_robust(
167                 user=request.user,
168                 project=project,
169                 rule=rule,
170                 rule_type="issue",
171                 sender=self,
172                 is_api_token=request.auth is not None,
173             )
174 
175             return Response(serialize(updated_rule, request.user))
176 
177         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
178 
179     @transaction_start("ProjectRuleDetailsEndpoint")
180     def delete(self, request: Request, project, rule) -> Response:
181         """
182         Delete a rule
183         """
184         rule.update(status=ObjectStatus.PENDING_DELETION)
185         RuleActivity.objects.create(
186             rule=rule, user_id=request.user.id, type=RuleActivityType.DELETED.value
187         )
188         scheduled = RegionScheduledDeletion.schedule(rule, days=0, actor=request.user)
189         self.create_audit_entry(
190             request=request,
191             organization=project.organization,
192             target_object=rule.id,
193             event=audit_log.get_event_id("RULE_REMOVE"),
194             data=rule.get_audit_log_data(),
195             transaction_id=scheduled.id,
196         )
197         return Response(status=202)
198
```

Path: `src/sentry/api/endpoints/project_rules.py`
Content:
```
1 from django.conf import settings
2 from django.db.models.signals import pre_save
3 from django.dispatch import receiver
4 from rest_framework import status
5 from rest_framework.request import Request
6 from rest_framework.response import Response
7 
8 from sentry import audit_log, features
9 from sentry.api.api_owners import ApiOwner
10 from sentry.api.base import region_silo_endpoint
11 from sentry.api.bases.project import ProjectAlertRulePermission, ProjectEndpoint
12 from sentry.api.serializers import serialize
13 from sentry.api.serializers.rest_framework.rule import RuleSerializer
14 from sentry.constants import ObjectStatus
15 from sentry.integrations.slack.utils import RedisRuleStatus
16 from sentry.mediators import project_rules
17 from sentry.models import Rule, RuleActivity, RuleActivityType, Team, User
18 from sentry.rules.actions import trigger_sentry_app_action_creators_for_issues
19 from sentry.rules.processor import is_condition_slow
20 from sentry.signals import alert_rule_created
21 from sentry.tasks.integrations.slack import find_channel_id_for_rule
22 from sentry.web.decorators import transaction_start
23 
24 
25 def clean_rule_data(data):
26     for datum in data:
27         if datum.get("name"):
28             del datum["name"]
29 
30 
31 @receiver(pre_save, sender=Rule)
32 def pre_save_rule(instance, sender, *args, **kwargs):
33     clean_rule_data(instance.data.get("conditions", []))
34     clean_rule_data(instance.data.get("actions", []))
35 
36 
37 @region_silo_endpoint
38 class ProjectRulesEndpoint(ProjectEndpoint):
39     owner = ApiOwner.ISSUES
40     permission_classes = (ProjectAlertRulePermission,)
41 
42     @transaction_start("ProjectRulesEndpoint")
43     def get(self, request: Request, project) -> Response:
44         """
45         List a project's rules
46 
47         Retrieve a list of rules for a given project.
48 
49             {method} {path}
50 
51         """
52         queryset = Rule.objects.filter(
53             project=project,
54             status=ObjectStatus.ACTIVE,
55         ).select_related("project")
56 
57         return self.paginate(
58             request=request,
59             queryset=queryset,
60             order_by="-id",
61             on_results=lambda x: serialize(x, request.user),
62         )
63 
64     @transaction_start("ProjectRulesEndpoint")
65     def post(self, request: Request, project) -> Response:
66         """
67         Create a rule
68 
69         Create a new rule for the given project.
70 
71             {method} {path}
72             {{
73               "name": "My rule name",
74               "owner": "type:id",
75               "conditions": [],
76               "filters": [],
77               "actions": [],
78               "actionMatch": "all",
79               "filterMatch": "all"
80             }}
81 
82         """
83 
84         serializer = RuleSerializer(
85             context={"project": project, "organization": project.organization}, data=request.data
86         )
87         if not serializer.is_valid():
88             return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
89 
90         data = serializer.validated_data
91         # combine filters and conditions into one conditions criteria for the rule object
92         conditions = data.get("conditions", [])
93         if "filters" in data:
94             conditions.extend(data["filters"])
95 
96         new_rule_is_slow = False
97         for condition in conditions:
98             if is_condition_slow(condition):
99                 new_rule_is_slow = True
100                 break
101 
102         rules = Rule.objects.filter(project=project, status=ObjectStatus.ACTIVE)
103         slow_rules = 0
104         for rule in rules:
105             for condition in rule.data["conditions"]:
106                 if is_condition_slow(condition):
107                     slow_rules += 1
108                     break
109 
110         if new_rule_is_slow:
111             max_slow_alerts = settings.MAX_SLOW_CONDITION_ISSUE_ALERTS
112             if features.has("organizations:more-slow-alerts", project.organization):
113                 max_slow_alerts = settings.MAX_MORE_SLOW_CONDITION_ISSUE_ALERTS
114             if slow_rules >= max_slow_alerts:
115                 return Response(
116                     {
117                         "conditions": [
118                             f"You may not exceed {max_slow_alerts} rules with this type of condition per project.",
119                         ]
120                     },
121                     status=status.HTTP_400_BAD_REQUEST,
122                 )
123         if (
124             not new_rule_is_slow
125             and (len(rules) - slow_rules) >= settings.MAX_FAST_CONDITION_ISSUE_ALERTS
126         ):
127             return Response(
128                 {
129                     "conditions": [
130                         f"You may not exceed {settings.MAX_FAST_CONDITION_ISSUE_ALERTS} rules with this type of condition per project.",
131                     ]
132                 },
133                 status=status.HTTP_400_BAD_REQUEST,
134             )
135 
136         kwargs = {
137             "name": data["name"],
138             "environment": data.get("environment"),
139             "project": project,
140             "action_match": data["actionMatch"],
141             "filter_match": data.get("filterMatch"),
142             "conditions": conditions,
143             "actions": data.get("actions", []),
144             "frequency": data.get("frequency"),
145             "user_id": request.user.id,
146         }
147         owner = data.get("owner")
148         if owner:
149             try:
150                 kwargs["owner"] = owner.resolve_to_actor().id
151             except (User.DoesNotExist, Team.DoesNotExist):
152                 return Response(
153                     "Could not resolve owner",
154                     status=status.HTTP_400_BAD_REQUEST,
155                 )
156 
157         if data.get("pending_save"):
158             client = RedisRuleStatus()
159             uuid_context = {"uuid": client.uuid}
160             kwargs.update(uuid_context)
161             find_channel_id_for_rule.apply_async(kwargs=kwargs)
162             return Response(uuid_context, status=202)
163 
164         created_alert_rule_ui_component = trigger_sentry_app_action_creators_for_issues(
165             kwargs.get("actions")
166         )
167         rule = project_rules.Creator.run(request=request, **kwargs)
168         RuleActivity.objects.create(
169             rule=rule, user_id=request.user.id, type=RuleActivityType.CREATED.value
170         )
171         duplicate_rule = request.query_params.get("duplicateRule")
172         wizard_v3 = request.query_params.get("wizardV3")
173 
174         self.create_audit_entry(
175             request=request,
176             organization=project.organization,
177             target_object=rule.id,
178             event=audit_log.get_event_id("RULE_ADD"),
179             data=rule.get_audit_log_data(),
180         )
181         alert_rule_created.send_robust(
182             user=request.user,
183             project=project,
184             rule=rule,
185             rule_type="issue",
186             sender=self,
187             is_api_token=request.auth is not None,
188             alert_rule_ui_component=created_alert_rule_ui_component,
189             duplicate_rule=duplicate_rule,
190             wizard_v3=wizard_v3,
191         )
192 
193         return Response(serialize(rule, request.user))
194
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/sentry/api/endpoints/project_rule_details.py b/src/sentry/api/endpoints/project_rule_details.py
--- a/src/sentry/api/endpoints/project_rule_details.py
+++ b/src/sentry/api/endpoints/project_rule_details.py
@@ -7,6 +7,7 @@
 from sentry import audit_log
 from sentry.api.base import region_silo_endpoint
 from sentry.api.bases.rule import RuleEndpoint
+from sentry.api.endpoints.project_rules import find_duplicate_rule
 from sentry.api.serializers import serialize
 from sentry.api.serializers.models.rule import RuleSerializer
 from sentry.api.serializers.rest_framework.rule import RuleSerializer as DrfRuleSerializer
@@ -116,6 +117,16 @@
         if serializer.is_valid():
             data = serializer.validated_data
 
+            if not data.get("actions", []):
+                return Response(
+                    {
+                        "actions": [
+                            "You must add an action for this alert to fire.",
+                        ]
+                    },
+                    status=status.HTTP_400_BAD_REQUEST,
+                )
+
             # combine filters and conditions into one conditions criteria for the rule object
             conditions = data.get("conditions", [])
             if "filters" in data:
@@ -131,6 +142,17 @@
                 "actions": data["actions"],
                 "frequency": data.get("frequency"),
             }
+            duplicate_rule = find_duplicate_rule(kwargs, project)
+            if duplicate_rule:
+                return Response(
+                    {
+                        "name": [
+                            f"This rule is an exact duplicate of '{duplicate_rule.label}' in this project and may not be created.",
+                        ]
+                    },
+                    status=status.HTTP_400_BAD_REQUEST,
+                )
+
             owner = data.get("owner")
             if owner:
                 try:
diff --git a/src/sentry/api/endpoints/project_rules.py b/src/sentry/api/endpoints/project_rules.py
--- a/src/sentry/api/endpoints/project_rules.py
+++ b/src/sentry/api/endpoints/project_rules.py
@@ -34,6 +34,23 @@
     clean_rule_data(instance.data.get("actions", []))
 
 
+def find_duplicate_rule(rule_data, project):
+    matchers = [key for key in list(rule_data.keys()) if key not in ("name", "user_id")]
+    existing_rules = Rule.objects.filter(project=project, status=ObjectStatus.ACTIVE)
+    for existing_rule in existing_rules:
+        keys = 0
+        matches = 0
+        for matcher in matchers:
+            if existing_rule.data.get(matcher) and rule_data.get(matcher):
+                keys += 1
+                if existing_rule.data[matcher] == rule_data[matcher]:
+                    matches += 1
+
+        if keys == matches:
+            return existing_rule
+    return None
+
+
 @region_silo_endpoint
 class ProjectRulesEndpoint(ProjectEndpoint):
     owner = ApiOwner.ISSUES
@@ -88,6 +105,16 @@
             return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 
         data = serializer.validated_data
+
+        if not data.get("actions", []):
+            return Response(
+                {
+                    "actions": [
+                        "You must add an action for this alert to fire.",
+                    ]
+                },
+                status=status.HTTP_400_BAD_REQUEST,
+            )
         # combine filters and conditions into one conditions criteria for the rule object
         conditions = data.get("conditions", [])
         if "filters" in data:
@@ -144,6 +171,17 @@
             "frequency": data.get("frequency"),
             "user_id": request.user.id,
         }
+        duplicate_rule = find_duplicate_rule(kwargs, project)
+        if duplicate_rule:
+            return Response(
+                {
+                    "name": [
+                        f"This rule is an exact duplicate of '{duplicate_rule.label}' in this project and may not be created.",
+                    ]
+                },
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+
         owner = data.get("owner")
         if owner:
             try:
@@ -165,6 +203,7 @@
             kwargs.get("actions")
         )
         rule = project_rules.Creator.run(request=request, **kwargs)
+
         RuleActivity.objects.create(
             rule=rule, user_id=request.user.id, type=RuleActivityType.CREATED.value
         )

{"golden_diff": "diff --git a/src/sentry/api/endpoints/project_rule_details.py b/src/sentry/api/endpoints/project_rule_details.py\n--- a/src/sentry/api/endpoints/project_rule_details.py\n+++ b/src/sentry/api/endpoints/project_rule_details.py\n@@ -7,6 +7,7 @@\n from sentry import audit_log\n from sentry.api.base import region_silo_endpoint\n from sentry.api.bases.rule import RuleEndpoint\n+from sentry.api.endpoints.project_rules import find_duplicate_rule\n from sentry.api.serializers import serialize\n from sentry.api.serializers.models.rule import RuleSerializer\n from sentry.api.serializers.rest_framework.rule import RuleSerializer as DrfRuleSerializer\n@@ -116,6 +117,16 @@\n         if serializer.is_valid():\n             data = serializer.validated_data\n \n+            if not data.get(\"actions\", []):\n+                return Response(\n+                    {\n+                        \"actions\": [\n+                            \"You must add an action for this alert to fire.\",\n+                        ]\n+                    },\n+                    status=status.HTTP_400_BAD_REQUEST,\n+                )\n+\n             # combine filters and conditions into one conditions criteria for the rule object\n             conditions = data.get(\"conditions\", [])\n             if \"filters\" in data:\n@@ -131,6 +142,17 @@\n                 \"actions\": data[\"actions\"],\n                 \"frequency\": data.get(\"frequency\"),\n             }\n+            duplicate_rule = find_duplicate_rule(kwargs, project)\n+            if duplicate_rule:\n+                return Response(\n+                    {\n+                        \"name\": [\n+                            f\"This rule is an exact duplicate of '{duplicate_rule.label}' in this project and may not be created.\",\n+                        ]\n+                    },\n+                    status=status.HTTP_400_BAD_REQUEST,\n+                )\n+\n             owner = data.get(\"owner\")\n             if owner:\n                 try:\ndiff --git a/src/sentry/api/endpoints/project_rules.py b/src/sentry/api/endpoints/project_rules.py\n--- a/src/sentry/api/endpoints/project_rules.py\n+++ b/src/sentry/api/endpoints/project_rules.py\n@@ -34,6 +34,23 @@\n     clean_rule_data(instance.data.get(\"actions\", []))\n \n \n+def find_duplicate_rule(rule_data, project):\n+    matchers = [key for key in list(rule_data.keys()) if key not in (\"name\", \"user_id\")]\n+    existing_rules = Rule.objects.filter(project=project, status=ObjectStatus.ACTIVE)\n+    for existing_rule in existing_rules:\n+        keys = 0\n+        matches = 0\n+        for matcher in matchers:\n+            if existing_rule.data.get(matcher) and rule_data.get(matcher):\n+                keys += 1\n+                if existing_rule.data[matcher] == rule_data[matcher]:\n+                    matches += 1\n+\n+        if keys == matches:\n+            return existing_rule\n+    return None\n+\n+\n @region_silo_endpoint\n class ProjectRulesEndpoint(ProjectEndpoint):\n     owner = ApiOwner.ISSUES\n@@ -88,6 +105,16 @@\n             return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n \n         data = serializer.validated_data\n+\n+        if not data.get(\"actions\", []):\n+            return Response(\n+                {\n+                    \"actions\": [\n+                        \"You must add an action for this alert to fire.\",\n+                    ]\n+                },\n+                status=status.HTTP_400_BAD_REQUEST,\n+            )\n         # combine filters and conditions into one conditions criteria for the rule object\n         conditions = data.get(\"conditions\", [])\n         if \"filters\" in data:\n@@ -144,6 +171,17 @@\n             \"frequency\": data.get(\"frequency\"),\n             \"user_id\": request.user.id,\n         }\n+        duplicate_rule = find_duplicate_rule(kwargs, project)\n+        if duplicate_rule:\n+            return Response(\n+                {\n+                    \"name\": [\n+                        f\"This rule is an exact duplicate of '{duplicate_rule.label}' in this project and may not be created.\",\n+                    ]\n+                },\n+                status=status.HTTP_400_BAD_REQUEST,\n+            )\n+\n         owner = data.get(\"owner\")\n         if owner:\n             try:\n@@ -165,6 +203,7 @@\n             kwargs.get(\"actions\")\n         )\n         rule = project_rules.Creator.run(request=request, **kwargs)\n+\n         RuleActivity.objects.create(\n             rule=rule, user_id=request.user.id, type=RuleActivityType.CREATED.value\n         )\n", "issue": "Prevent duplicate and no action alerts from being created\nAlerts with no actions are useless as are alerts that are exact duplicates of another rule in the same project, so we should not allow them to be saved.\n", "before_files": [{"content": "from __future__ import annotations\n\nfrom rest_framework import status\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\n\nfrom sentry import audit_log\nfrom sentry.api.base import region_silo_endpoint\nfrom sentry.api.bases.rule import RuleEndpoint\nfrom sentry.api.serializers import serialize\nfrom sentry.api.serializers.models.rule import RuleSerializer\nfrom sentry.api.serializers.rest_framework.rule import RuleSerializer as DrfRuleSerializer\nfrom sentry.constants import ObjectStatus\nfrom sentry.integrations.slack.utils import RedisRuleStatus\nfrom sentry.mediators import project_rules\nfrom sentry.models import (\n    RegionScheduledDeletion,\n    RuleActivity,\n    RuleActivityType,\n    SentryAppComponent,\n    Team,\n    User,\n)\nfrom sentry.models.integrations.sentry_app_installation import (\n    SentryAppInstallation,\n    prepare_ui_component,\n)\nfrom sentry.rules.actions import trigger_sentry_app_action_creators_for_issues\nfrom sentry.signals import alert_rule_edited\nfrom sentry.tasks.integrations.slack import find_channel_id_for_rule\nfrom sentry.web.decorators import transaction_start\n\n\n@region_silo_endpoint\nclass ProjectRuleDetailsEndpoint(RuleEndpoint):\n    @transaction_start(\"ProjectRuleDetailsEndpoint\")\n    def get(self, request: Request, project, rule) -> Response:\n        \"\"\"\n        Retrieve a rule\n\n        Return details on an individual rule.\n\n            {method} {path}\n\n        \"\"\"\n\n        # Serialize Rule object\n        serialized_rule = serialize(\n            rule, request.user, RuleSerializer(request.GET.getlist(\"expand\", []))\n        )\n\n        errors = []\n        # Prepare Rule Actions that are SentryApp components using the meta fields\n        for action in serialized_rule.get(\"actions\", []):\n            if action.get(\"_sentry_app_installation\") and action.get(\"_sentry_app_component\"):\n                installation = SentryAppInstallation(**action.get(\"_sentry_app_installation\", {}))\n                component = prepare_ui_component(\n                    installation,\n                    SentryAppComponent(**action.get(\"_sentry_app_component\")),\n                    project.slug,\n                    action.get(\"settings\"),\n                )\n\n                if component is None:\n                    errors.append(\n                        {\"detail\": f\"Could not fetch details from {installation.sentry_app.name}\"}\n                    )\n                    action[\"disabled\"] = True\n                    continue\n\n                action[\"formFields\"] = component.schema.get(\"settings\", {})\n\n                # Delete meta fields\n                del action[\"_sentry_app_installation\"]\n                del action[\"_sentry_app_component\"]\n\n            # TODO(nisanthan): This is a temporary fix. We need to save both the label and value of the selected choice and not save all the choices.\n            if action.get(\"id\") == \"sentry.integrations.jira.notify_action.JiraCreateTicketAction\":\n                for field in action.get(\"dynamic_form_fields\", []):\n                    if field.get(\"choices\"):\n                        field[\"choices\"] = [\n                            p\n                            for p in field.get(\"choices\", [])\n                            if isinstance(p[0], str) and isinstance(p[1], str)\n                        ]\n\n        if len(errors):\n            serialized_rule[\"errors\"] = errors\n\n        return Response(serialized_rule)\n\n    @transaction_start(\"ProjectRuleDetailsEndpoint\")\n    def put(self, request: Request, project, rule) -> Response:\n        \"\"\"\n        Update a rule\n\n        Update various attributes for the given rule.\n\n            {method} {path}\n            {{\n              \"name\": \"My rule name\",\n              \"conditions\": [],\n              \"filters\": [],\n              \"actions\": [],\n              \"actionMatch\": \"all\",\n              \"filterMatch\": \"all\"\n            }}\n\n        \"\"\"\n        serializer = DrfRuleSerializer(\n            context={\"project\": project, \"organization\": project.organization},\n            data=request.data,\n            partial=True,\n        )\n\n        if serializer.is_valid():\n            data = serializer.validated_data\n\n            # combine filters and conditions into one conditions criteria for the rule object\n            conditions = data.get(\"conditions\", [])\n            if \"filters\" in data:\n                conditions.extend(data[\"filters\"])\n\n            kwargs = {\n                \"name\": data[\"name\"],\n                \"environment\": data.get(\"environment\"),\n                \"project\": project,\n                \"action_match\": data[\"actionMatch\"],\n                \"filter_match\": data.get(\"filterMatch\"),\n                \"conditions\": conditions,\n                \"actions\": data[\"actions\"],\n                \"frequency\": data.get(\"frequency\"),\n            }\n            owner = data.get(\"owner\")\n            if owner:\n                try:\n                    kwargs[\"owner\"] = owner.resolve_to_actor().id\n                except (User.DoesNotExist, Team.DoesNotExist):\n                    return Response(\n                        \"Could not resolve owner\",\n                        status=status.HTTP_400_BAD_REQUEST,\n                    )\n\n            if data.get(\"pending_save\"):\n                client = RedisRuleStatus()\n                kwargs.update({\"uuid\": client.uuid, \"rule_id\": rule.id})\n                find_channel_id_for_rule.apply_async(kwargs=kwargs)\n\n                context = {\"uuid\": client.uuid}\n                return Response(context, status=202)\n\n            trigger_sentry_app_action_creators_for_issues(actions=kwargs.get(\"actions\"))\n\n            updated_rule = project_rules.Updater.run(rule=rule, request=request, **kwargs)\n\n            RuleActivity.objects.create(\n                rule=updated_rule, user_id=request.user.id, type=RuleActivityType.UPDATED.value\n            )\n            self.create_audit_entry(\n                request=request,\n                organization=project.organization,\n                target_object=updated_rule.id,\n                event=audit_log.get_event_id(\"RULE_EDIT\"),\n                data=updated_rule.get_audit_log_data(),\n            )\n            alert_rule_edited.send_robust(\n                user=request.user,\n                project=project,\n                rule=rule,\n                rule_type=\"issue\",\n                sender=self,\n                is_api_token=request.auth is not None,\n            )\n\n            return Response(serialize(updated_rule, request.user))\n\n        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n\n    @transaction_start(\"ProjectRuleDetailsEndpoint\")\n    def delete(self, request: Request, project, rule) -> Response:\n        \"\"\"\n        Delete a rule\n        \"\"\"\n        rule.update(status=ObjectStatus.PENDING_DELETION)\n        RuleActivity.objects.create(\n            rule=rule, user_id=request.user.id, type=RuleActivityType.DELETED.value\n        )\n        scheduled = RegionScheduledDeletion.schedule(rule, days=0, actor=request.user)\n        self.create_audit_entry(\n            request=request,\n            organization=project.organization,\n            target_object=rule.id,\n            event=audit_log.get_event_id(\"RULE_REMOVE\"),\n            data=rule.get_audit_log_data(),\n            transaction_id=scheduled.id,\n        )\n        return Response(status=202)\n", "path": "src/sentry/api/endpoints/project_rule_details.py"}, {"content": "from django.conf import settings\nfrom django.db.models.signals import pre_save\nfrom django.dispatch import receiver\nfrom rest_framework import status\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\n\nfrom sentry import audit_log, features\nfrom sentry.api.api_owners import ApiOwner\nfrom sentry.api.base import region_silo_endpoint\nfrom sentry.api.bases.project import ProjectAlertRulePermission, ProjectEndpoint\nfrom sentry.api.serializers import serialize\nfrom sentry.api.serializers.rest_framework.rule import RuleSerializer\nfrom sentry.constants import ObjectStatus\nfrom sentry.integrations.slack.utils import RedisRuleStatus\nfrom sentry.mediators import project_rules\nfrom sentry.models import Rule, RuleActivity, RuleActivityType, Team, User\nfrom sentry.rules.actions import trigger_sentry_app_action_creators_for_issues\nfrom sentry.rules.processor import is_condition_slow\nfrom sentry.signals import alert_rule_created\nfrom sentry.tasks.integrations.slack import find_channel_id_for_rule\nfrom sentry.web.decorators import transaction_start\n\n\ndef clean_rule_data(data):\n    for datum in data:\n        if datum.get(\"name\"):\n            del datum[\"name\"]\n\n\n@receiver(pre_save, sender=Rule)\ndef pre_save_rule(instance, sender, *args, **kwargs):\n    clean_rule_data(instance.data.get(\"conditions\", []))\n    clean_rule_data(instance.data.get(\"actions\", []))\n\n\n@region_silo_endpoint\nclass ProjectRulesEndpoint(ProjectEndpoint):\n    owner = ApiOwner.ISSUES\n    permission_classes = (ProjectAlertRulePermission,)\n\n    @transaction_start(\"ProjectRulesEndpoint\")\n    def get(self, request: Request, project) -> Response:\n        \"\"\"\n        List a project's rules\n\n        Retrieve a list of rules for a given project.\n\n            {method} {path}\n\n        \"\"\"\n        queryset = Rule.objects.filter(\n            project=project,\n            status=ObjectStatus.ACTIVE,\n        ).select_related(\"project\")\n\n        return self.paginate(\n            request=request,\n            queryset=queryset,\n            order_by=\"-id\",\n            on_results=lambda x: serialize(x, request.user),\n        )\n\n    @transaction_start(\"ProjectRulesEndpoint\")\n    def post(self, request: Request, project) -> Response:\n        \"\"\"\n        Create a rule\n\n        Create a new rule for the given project.\n\n            {method} {path}\n            {{\n              \"name\": \"My rule name\",\n              \"owner\": \"type:id\",\n              \"conditions\": [],\n              \"filters\": [],\n              \"actions\": [],\n              \"actionMatch\": \"all\",\n              \"filterMatch\": \"all\"\n            }}\n\n        \"\"\"\n\n        serializer = RuleSerializer(\n            context={\"project\": project, \"organization\": project.organization}, data=request.data\n        )\n        if not serializer.is_valid():\n            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n\n        data = serializer.validated_data\n        # combine filters and conditions into one conditions criteria for the rule object\n        conditions = data.get(\"conditions\", [])\n        if \"filters\" in data:\n            conditions.extend(data[\"filters\"])\n\n        new_rule_is_slow = False\n        for condition in conditions:\n            if is_condition_slow(condition):\n                new_rule_is_slow = True\n                break\n\n        rules = Rule.objects.filter(project=project, status=ObjectStatus.ACTIVE)\n        slow_rules = 0\n        for rule in rules:\n            for condition in rule.data[\"conditions\"]:\n                if is_condition_slow(condition):\n                    slow_rules += 1\n                    break\n\n        if new_rule_is_slow:\n            max_slow_alerts = settings.MAX_SLOW_CONDITION_ISSUE_ALERTS\n            if features.has(\"organizations:more-slow-alerts\", project.organization):\n                max_slow_alerts = settings.MAX_MORE_SLOW_CONDITION_ISSUE_ALERTS\n            if slow_rules >= max_slow_alerts:\n                return Response(\n                    {\n                        \"conditions\": [\n                            f\"You may not exceed {max_slow_alerts} rules with this type of condition per project.\",\n                        ]\n                    },\n                    status=status.HTTP_400_BAD_REQUEST,\n                )\n        if (\n            not new_rule_is_slow\n            and (len(rules) - slow_rules) >= settings.MAX_FAST_CONDITION_ISSUE_ALERTS\n        ):\n            return Response(\n                {\n                    \"conditions\": [\n                        f\"You may not exceed {settings.MAX_FAST_CONDITION_ISSUE_ALERTS} rules with this type of condition per project.\",\n                    ]\n                },\n                status=status.HTTP_400_BAD_REQUEST,\n            )\n\n        kwargs = {\n            \"name\": data[\"name\"],\n            \"environment\": data.get(\"environment\"),\n            \"project\": project,\n            \"action_match\": data[\"actionMatch\"],\n            \"filter_match\": data.get(\"filterMatch\"),\n            \"conditions\": conditions,\n            \"actions\": data.get(\"actions\", []),\n            \"frequency\": data.get(\"frequency\"),\n            \"user_id\": request.user.id,\n        }\n        owner = data.get(\"owner\")\n        if owner:\n            try:\n                kwargs[\"owner\"] = owner.resolve_to_actor().id\n            except (User.DoesNotExist, Team.DoesNotExist):\n                return Response(\n                    \"Could not resolve owner\",\n                    status=status.HTTP_400_BAD_REQUEST,\n                )\n\n        if data.get(\"pending_save\"):\n            client = RedisRuleStatus()\n            uuid_context = {\"uuid\": client.uuid}\n            kwargs.update(uuid_context)\n            find_channel_id_for_rule.apply_async(kwargs=kwargs)\n            return Response(uuid_context, status=202)\n\n        created_alert_rule_ui_component = trigger_sentry_app_action_creators_for_issues(\n            kwargs.get(\"actions\")\n        )\n        rule = project_rules.Creator.run(request=request, **kwargs)\n        RuleActivity.objects.create(\n            rule=rule, user_id=request.user.id, type=RuleActivityType.CREATED.value\n        )\n        duplicate_rule = request.query_params.get(\"duplicateRule\")\n        wizard_v3 = request.query_params.get(\"wizardV3\")\n\n        self.create_audit_entry(\n            request=request,\n            organization=project.organization,\n            target_object=rule.id,\n            event=audit_log.get_event_id(\"RULE_ADD\"),\n            data=rule.get_audit_log_data(),\n        )\n        alert_rule_created.send_robust(\n            user=request.user,\n            project=project,\n            rule=rule,\n            rule_type=\"issue\",\n            sender=self,\n            is_api_token=request.auth is not None,\n            alert_rule_ui_component=created_alert_rule_ui_component,\n            duplicate_rule=duplicate_rule,\n            wizard_v3=wizard_v3,\n        )\n\n        return Response(serialize(rule, request.user))\n", "path": "src/sentry/api/endpoints/project_rules.py"}], "after_files": [{"content": "from __future__ import annotations\n\nfrom rest_framework import status\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\n\nfrom sentry import audit_log\nfrom sentry.api.base import region_silo_endpoint\nfrom sentry.api.bases.rule import RuleEndpoint\nfrom sentry.api.endpoints.project_rules import find_duplicate_rule\nfrom sentry.api.serializers import serialize\nfrom sentry.api.serializers.models.rule import RuleSerializer\nfrom sentry.api.serializers.rest_framework.rule import RuleSerializer as DrfRuleSerializer\nfrom sentry.constants import ObjectStatus\nfrom sentry.integrations.slack.utils import RedisRuleStatus\nfrom sentry.mediators import project_rules\nfrom sentry.models import (\n    RegionScheduledDeletion,\n    RuleActivity,\n    RuleActivityType,\n    SentryAppComponent,\n    Team,\n    User,\n)\nfrom sentry.models.integrations.sentry_app_installation import (\n    SentryAppInstallation,\n    prepare_ui_component,\n)\nfrom sentry.rules.actions import trigger_sentry_app_action_creators_for_issues\nfrom sentry.signals import alert_rule_edited\nfrom sentry.tasks.integrations.slack import find_channel_id_for_rule\nfrom sentry.web.decorators import transaction_start\n\n\n@region_silo_endpoint\nclass ProjectRuleDetailsEndpoint(RuleEndpoint):\n    @transaction_start(\"ProjectRuleDetailsEndpoint\")\n    def get(self, request: Request, project, rule) -> Response:\n        \"\"\"\n        Retrieve a rule\n\n        Return details on an individual rule.\n\n            {method} {path}\n\n        \"\"\"\n\n        # Serialize Rule object\n        serialized_rule = serialize(\n            rule, request.user, RuleSerializer(request.GET.getlist(\"expand\", []))\n        )\n\n        errors = []\n        # Prepare Rule Actions that are SentryApp components using the meta fields\n        for action in serialized_rule.get(\"actions\", []):\n            if action.get(\"_sentry_app_installation\") and action.get(\"_sentry_app_component\"):\n                installation = SentryAppInstallation(**action.get(\"_sentry_app_installation\", {}))\n                component = prepare_ui_component(\n                    installation,\n                    SentryAppComponent(**action.get(\"_sentry_app_component\")),\n                    project.slug,\n                    action.get(\"settings\"),\n                )\n\n                if component is None:\n                    errors.append(\n                        {\"detail\": f\"Could not fetch details from {installation.sentry_app.name}\"}\n                    )\n                    action[\"disabled\"] = True\n                    continue\n\n                action[\"formFields\"] = component.schema.get(\"settings\", {})\n\n                # Delete meta fields\n                del action[\"_sentry_app_installation\"]\n                del action[\"_sentry_app_component\"]\n\n            # TODO(nisanthan): This is a temporary fix. We need to save both the label and value of the selected choice and not save all the choices.\n            if action.get(\"id\") == \"sentry.integrations.jira.notify_action.JiraCreateTicketAction\":\n                for field in action.get(\"dynamic_form_fields\", []):\n                    if field.get(\"choices\"):\n                        field[\"choices\"] = [\n                            p\n                            for p in field.get(\"choices\", [])\n                            if isinstance(p[0], str) and isinstance(p[1], str)\n                        ]\n\n        if len(errors):\n            serialized_rule[\"errors\"] = errors\n\n        return Response(serialized_rule)\n\n    @transaction_start(\"ProjectRuleDetailsEndpoint\")\n    def put(self, request: Request, project, rule) -> Response:\n        \"\"\"\n        Update a rule\n\n        Update various attributes for the given rule.\n\n            {method} {path}\n            {{\n              \"name\": \"My rule name\",\n              \"conditions\": [],\n              \"filters\": [],\n              \"actions\": [],\n              \"actionMatch\": \"all\",\n              \"filterMatch\": \"all\"\n            }}\n\n        \"\"\"\n        serializer = DrfRuleSerializer(\n            context={\"project\": project, \"organization\": project.organization},\n            data=request.data,\n            partial=True,\n        )\n\n        if serializer.is_valid():\n            data = serializer.validated_data\n\n            if not data.get(\"actions\", []):\n                return Response(\n                    {\n                        \"actions\": [\n                            \"You must add an action for this alert to fire.\",\n                        ]\n                    },\n                    status=status.HTTP_400_BAD_REQUEST,\n                )\n\n            # combine filters and conditions into one conditions criteria for the rule object\n            conditions = data.get(\"conditions\", [])\n            if \"filters\" in data:\n                conditions.extend(data[\"filters\"])\n\n            kwargs = {\n                \"name\": data[\"name\"],\n                \"environment\": data.get(\"environment\"),\n                \"project\": project,\n                \"action_match\": data[\"actionMatch\"],\n                \"filter_match\": data.get(\"filterMatch\"),\n                \"conditions\": conditions,\n                \"actions\": data[\"actions\"],\n                \"frequency\": data.get(\"frequency\"),\n            }\n            duplicate_rule = find_duplicate_rule(kwargs, project)\n            if duplicate_rule:\n                return Response(\n                    {\n                        \"name\": [\n                            f\"This rule is an exact duplicate of '{duplicate_rule.label}' in this project and may not be created.\",\n                        ]\n                    },\n                    status=status.HTTP_400_BAD_REQUEST,\n                )\n\n            owner = data.get(\"owner\")\n            if owner:\n                try:\n                    kwargs[\"owner\"] = owner.resolve_to_actor().id\n                except (User.DoesNotExist, Team.DoesNotExist):\n                    return Response(\n                        \"Could not resolve owner\",\n                        status=status.HTTP_400_BAD_REQUEST,\n                    )\n\n            if data.get(\"pending_save\"):\n                client = RedisRuleStatus()\n                kwargs.update({\"uuid\": client.uuid, \"rule_id\": rule.id})\n                find_channel_id_for_rule.apply_async(kwargs=kwargs)\n\n                context = {\"uuid\": client.uuid}\n                return Response(context, status=202)\n\n            trigger_sentry_app_action_creators_for_issues(actions=kwargs.get(\"actions\"))\n\n            updated_rule = project_rules.Updater.run(rule=rule, request=request, **kwargs)\n\n            RuleActivity.objects.create(\n                rule=updated_rule, user_id=request.user.id, type=RuleActivityType.UPDATED.value\n            )\n            self.create_audit_entry(\n                request=request,\n                organization=project.organization,\n                target_object=updated_rule.id,\n                event=audit_log.get_event_id(\"RULE_EDIT\"),\n                data=updated_rule.get_audit_log_data(),\n            )\n            alert_rule_edited.send_robust(\n                user=request.user,\n                project=project,\n                rule=rule,\n                rule_type=\"issue\",\n                sender=self,\n                is_api_token=request.auth is not None,\n            )\n\n            return Response(serialize(updated_rule, request.user))\n\n        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n\n    @transaction_start(\"ProjectRuleDetailsEndpoint\")\n    def delete(self, request: Request, project, rule) -> Response:\n        \"\"\"\n        Delete a rule\n        \"\"\"\n        rule.update(status=ObjectStatus.PENDING_DELETION)\n        RuleActivity.objects.create(\n            rule=rule, user_id=request.user.id, type=RuleActivityType.DELETED.value\n        )\n        scheduled = RegionScheduledDeletion.schedule(rule, days=0, actor=request.user)\n        self.create_audit_entry(\n            request=request,\n            organization=project.organization,\n            target_object=rule.id,\n            event=audit_log.get_event_id(\"RULE_REMOVE\"),\n            data=rule.get_audit_log_data(),\n            transaction_id=scheduled.id,\n        )\n        return Response(status=202)\n", "path": "src/sentry/api/endpoints/project_rule_details.py"}, {"content": "from django.conf import settings\nfrom django.db.models.signals import pre_save\nfrom django.dispatch import receiver\nfrom rest_framework import status\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\n\nfrom sentry import audit_log, features\nfrom sentry.api.api_owners import ApiOwner\nfrom sentry.api.base import region_silo_endpoint\nfrom sentry.api.bases.project import ProjectAlertRulePermission, ProjectEndpoint\nfrom sentry.api.serializers import serialize\nfrom sentry.api.serializers.rest_framework.rule import RuleSerializer\nfrom sentry.constants import ObjectStatus\nfrom sentry.integrations.slack.utils import RedisRuleStatus\nfrom sentry.mediators import project_rules\nfrom sentry.models import Rule, RuleActivity, RuleActivityType, Team, User\nfrom sentry.rules.actions import trigger_sentry_app_action_creators_for_issues\nfrom sentry.rules.processor import is_condition_slow\nfrom sentry.signals import alert_rule_created\nfrom sentry.tasks.integrations.slack import find_channel_id_for_rule\nfrom sentry.web.decorators import transaction_start\n\n\ndef clean_rule_data(data):\n    for datum in data:\n        if datum.get(\"name\"):\n            del datum[\"name\"]\n\n\n@receiver(pre_save, sender=Rule)\ndef pre_save_rule(instance, sender, *args, **kwargs):\n    clean_rule_data(instance.data.get(\"conditions\", []))\n    clean_rule_data(instance.data.get(\"actions\", []))\n\n\ndef find_duplicate_rule(rule_data, project):\n    matchers = [key for key in list(rule_data.keys()) if key not in (\"name\", \"user_id\")]\n    existing_rules = Rule.objects.filter(project=project, status=ObjectStatus.ACTIVE)\n    for existing_rule in existing_rules:\n        keys = 0\n        matches = 0\n        for matcher in matchers:\n            if existing_rule.data.get(matcher) and rule_data.get(matcher):\n                keys += 1\n                if existing_rule.data[matcher] == rule_data[matcher]:\n                    matches += 1\n\n        if keys == matches:\n            return existing_rule\n    return None\n\n\n@region_silo_endpoint\nclass ProjectRulesEndpoint(ProjectEndpoint):\n    owner = ApiOwner.ISSUES\n    permission_classes = (ProjectAlertRulePermission,)\n\n    @transaction_start(\"ProjectRulesEndpoint\")\n    def get(self, request: Request, project) -> Response:\n        \"\"\"\n        List a project's rules\n\n        Retrieve a list of rules for a given project.\n\n            {method} {path}\n\n        \"\"\"\n        queryset = Rule.objects.filter(\n            project=project,\n            status=ObjectStatus.ACTIVE,\n        ).select_related(\"project\")\n\n        return self.paginate(\n            request=request,\n            queryset=queryset,\n            order_by=\"-id\",\n            on_results=lambda x: serialize(x, request.user),\n        )\n\n    @transaction_start(\"ProjectRulesEndpoint\")\n    def post(self, request: Request, project) -> Response:\n        \"\"\"\n        Create a rule\n\n        Create a new rule for the given project.\n\n            {method} {path}\n            {{\n              \"name\": \"My rule name\",\n              \"owner\": \"type:id\",\n              \"conditions\": [],\n              \"filters\": [],\n              \"actions\": [],\n              \"actionMatch\": \"all\",\n              \"filterMatch\": \"all\"\n            }}\n\n        \"\"\"\n\n        serializer = RuleSerializer(\n            context={\"project\": project, \"organization\": project.organization}, data=request.data\n        )\n        if not serializer.is_valid():\n            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n\n        data = serializer.validated_data\n\n        if not data.get(\"actions\", []):\n            return Response(\n                {\n                    \"actions\": [\n                        \"You must add an action for this alert to fire.\",\n                    ]\n                },\n                status=status.HTTP_400_BAD_REQUEST,\n            )\n        # combine filters and conditions into one conditions criteria for the rule object\n        conditions = data.get(\"conditions\", [])\n        if \"filters\" in data:\n            conditions.extend(data[\"filters\"])\n\n        new_rule_is_slow = False\n        for condition in conditions:\n            if is_condition_slow(condition):\n                new_rule_is_slow = True\n                break\n\n        rules = Rule.objects.filter(project=project, status=ObjectStatus.ACTIVE)\n        slow_rules = 0\n        for rule in rules:\n            for condition in rule.data[\"conditions\"]:\n                if is_condition_slow(condition):\n                    slow_rules += 1\n                    break\n\n        if new_rule_is_slow:\n            max_slow_alerts = settings.MAX_SLOW_CONDITION_ISSUE_ALERTS\n            if features.has(\"organizations:more-slow-alerts\", project.organization):\n                max_slow_alerts = settings.MAX_MORE_SLOW_CONDITION_ISSUE_ALERTS\n            if slow_rules >= max_slow_alerts:\n                return Response(\n                    {\n                        \"conditions\": [\n                            f\"You may not exceed {max_slow_alerts} rules with this type of condition per project.\",\n                        ]\n                    },\n                    status=status.HTTP_400_BAD_REQUEST,\n                )\n        if (\n            not new_rule_is_slow\n            and (len(rules) - slow_rules) >= settings.MAX_FAST_CONDITION_ISSUE_ALERTS\n        ):\n            return Response(\n                {\n                    \"conditions\": [\n                        f\"You may not exceed {settings.MAX_FAST_CONDITION_ISSUE_ALERTS} rules with this type of condition per project.\",\n                    ]\n                },\n                status=status.HTTP_400_BAD_REQUEST,\n            )\n\n        kwargs = {\n            \"name\": data[\"name\"],\n            \"environment\": data.get(\"environment\"),\n            \"project\": project,\n            \"action_match\": data[\"actionMatch\"],\n            \"filter_match\": data.get(\"filterMatch\"),\n            \"conditions\": conditions,\n            \"actions\": data.get(\"actions\", []),\n            \"frequency\": data.get(\"frequency\"),\n            \"user_id\": request.user.id,\n        }\n        duplicate_rule = find_duplicate_rule(kwargs, project)\n        if duplicate_rule:\n            return Response(\n                {\n                    \"name\": [\n                        f\"This rule is an exact duplicate of '{duplicate_rule.label}' in this project and may not be created.\",\n                    ]\n                },\n                status=status.HTTP_400_BAD_REQUEST,\n            )\n\n        owner = data.get(\"owner\")\n        if owner:\n            try:\n                kwargs[\"owner\"] = owner.resolve_to_actor().id\n            except (User.DoesNotExist, Team.DoesNotExist):\n                return Response(\n                    \"Could not resolve owner\",\n                    status=status.HTTP_400_BAD_REQUEST,\n                )\n\n        if data.get(\"pending_save\"):\n            client = RedisRuleStatus()\n            uuid_context = {\"uuid\": client.uuid}\n            kwargs.update(uuid_context)\n            find_channel_id_for_rule.apply_async(kwargs=kwargs)\n            return Response(uuid_context, status=202)\n\n        created_alert_rule_ui_component = trigger_sentry_app_action_creators_for_issues(\n            kwargs.get(\"actions\")\n        )\n        rule = project_rules.Creator.run(request=request, **kwargs)\n\n        RuleActivity.objects.create(\n            rule=rule, user_id=request.user.id, type=RuleActivityType.CREATED.value\n        )\n        duplicate_rule = request.query_params.get(\"duplicateRule\")\n        wizard_v3 = request.query_params.get(\"wizardV3\")\n\n        self.create_audit_entry(\n            request=request,\n            organization=project.organization,\n            target_object=rule.id,\n            event=audit_log.get_event_id(\"RULE_ADD\"),\n            data=rule.get_audit_log_data(),\n        )\n        alert_rule_created.send_robust(\n            user=request.user,\n            project=project,\n            rule=rule,\n            rule_type=\"issue\",\n            sender=self,\n            is_api_token=request.auth is not None,\n            alert_rule_ui_component=created_alert_rule_ui_component,\n            duplicate_rule=duplicate_rule,\n            wizard_v3=wizard_v3,\n        )\n\n        return Response(serialize(rule, request.user))\n", "path": "src/sentry/api/endpoints/project_rules.py"}]}