Skip to content

Commit a80af8d

Browse files
authored
ref(alerts): Prevent name data from being saved to db (#54746)
Backend piece to #54739 to make sure we aren't saving the `name` field to the `Rule` table's `data` column.
1 parent 6b0e286 commit a80af8d

File tree

5 files changed

+58
-28
lines changed

5 files changed

+58
-28
lines changed

src/sentry/api/endpoints/project_rule_details.py

-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
from sentry.services.hybrid_cloud.user.service import user_service
3232
from sentry.signals import alert_rule_edited
3333
from sentry.tasks.integrations.slack import find_channel_id_for_rule
34-
from sentry.utils import metrics
3534
from sentry.web.decorators import transaction_start
3635

3736

@@ -171,8 +170,6 @@ def put(self, request: Request, project, rule) -> Response:
171170

172171
trigger_sentry_app_action_creators_for_issues(actions=kwargs.get("actions"))
173172

174-
if rule.data["conditions"] != kwargs["conditions"]:
175-
metrics.incr("sentry.issue_alert.conditions.edited", sample_rate=1.0)
176173
updated_rule = project_rules.Updater.run(rule=rule, request=request, **kwargs)
177174

178175
RuleActivity.objects.create(

src/sentry/api/serializers/rest_framework/rule.py

+9
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ def validate_environment(self, environment):
148148

149149
return environment
150150

151+
def validate_conditions(self, conditions):
152+
for condition in conditions:
153+
if condition.get("name"):
154+
del condition["name"]
155+
156+
return conditions
157+
151158
def validate(self, attrs):
152159
return super().validate(validate_actions(attrs))
153160

@@ -181,6 +188,8 @@ def validate_actions(attrs):
181188
# project_rule(_details) endpoints by setting it on attrs
182189
actions = attrs.get("actions", tuple())
183190
for action in actions:
191+
if action.get("name"):
192+
del action["name"]
184193
# XXX(colleen): For ticket rules we need to ensure the user has
185194
# at least done minimal configuration
186195
if action["id"] in TICKET_ACTIONS:

src/sentry/monitors/utils.py

-5
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,9 @@ def create_alert_rule_data(project: Project, user: User, monitor: Monitor, alert
228228
"conditions": [
229229
{
230230
"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
231-
"name": "A new issue is created",
232231
},
233232
{
234233
"id": "sentry.rules.conditions.regression_event.RegressionEventCondition",
235-
"name": "The issue changes state from resolved to unresolved",
236234
},
237235
],
238236
"createdBy": {
@@ -248,7 +246,6 @@ def create_alert_rule_data(project: Project, user: User, monitor: Monitor, alert
248246
"id": "sentry.rules.filters.tagged_event.TaggedEventFilter",
249247
"key": "monitor.slug",
250248
"match": "eq",
251-
"name": f"The event's tags match monitor.slug contains {monitor.slug}",
252249
"value": monitor.slug,
253250
}
254251
],
@@ -265,7 +262,6 @@ def create_alert_rule_data(project: Project, user: User, monitor: Monitor, alert
265262

266263
action = {
267264
"id": "sentry.mail.actions.NotifyEmailAction",
268-
"name": f"Send a notification to {target_type}",
269265
"targetIdentifier": target_identifier,
270266
"targetType": target_type,
271267
}
@@ -282,7 +278,6 @@ def update_alert_rule(request: Request, project: Project, alert_rule: Rule, aler
282278

283279
action = {
284280
"id": "sentry.mail.actions.NotifyEmailAction",
285-
"name": f"Send a notification to {target_type}",
286281
"targetIdentifier": target_identifier,
287282
"targetType": target_type,
288283
}

tests/sentry/api/endpoints/test_project_rule_details.py

+12-17
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
from datetime import datetime, timezone
44
from typing import Any, Mapping
5-
from unittest import mock
6-
from unittest.mock import call, patch
5+
from unittest.mock import patch
76

8-
import pytest
97
import responses
108
from freezegun import freeze_time
119

@@ -49,6 +47,8 @@ def assert_rule_from_payload(rule: Rule, payload: Mapping[str, Any]) -> None:
4947
# any(a.items() <= b.items()) to check if the payload dict is a subset of the rule.data dict
5048
# E.g. payload["actions"] = [{"name": "Test1"}], rule.data["actions"] = [{"name": "Test1", "id": 1}]
5149
for payload_action in payload.get("actions", []):
50+
if payload_action.get("name"):
51+
del payload_action["name"]
5252
# The Slack payload will contain '#channel' or '@user', but we save 'channel' or 'user' on the Rule
5353
if (
5454
payload_action["id"]
@@ -60,6 +60,8 @@ def assert_rule_from_payload(rule: Rule, payload: Mapping[str, Any]) -> None:
6060
)
6161
payload_conditions = payload.get("conditions", []) + payload.get("filters", [])
6262
for payload_condition in payload_conditions:
63+
if payload_condition.get("name"):
64+
del payload_condition["name"]
6365
assert any(
6466
payload_condition.items() <= rule_condition.items()
6567
for rule_condition in rule.data["conditions"]
@@ -102,6 +104,7 @@ def test_simple(self):
102104
)
103105
assert response.data["id"] == str(self.rule.id)
104106
assert response.data["environment"] is None
107+
assert response.data["conditions"][0]["name"]
105108

106109
def test_non_existing_rule(self):
107110
self.get_error_response(self.organization.slug, self.project.slug, 12345, status_code=404)
@@ -313,11 +316,6 @@ def test_with_jira_action_error(self):
313316

314317
@region_silo_test(stable=True)
315318
class UpdateProjectRuleTest(ProjectRuleDetailsBaseTestCase):
316-
@pytest.fixture(autouse=True)
317-
def _setup_metric_patch(self):
318-
with mock.patch("sentry.api.endpoints.project_rule_details.metrics") as self.metrics:
319-
yield
320-
321319
method = "PUT"
322320

323321
@patch("sentry.signals.alert_rule_edited.send_robust")
@@ -638,7 +636,12 @@ def test_rule_form_missing_action(self):
638636
)
639637

640638
def test_update_filters(self):
641-
conditions = [{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}]
639+
conditions = [
640+
{
641+
"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
642+
"name": "A new issue is created",
643+
}
644+
]
642645
filters = [
643646
{"id": "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter", "value": 10}
644647
]
@@ -735,10 +738,6 @@ def test_edit_condition_metric(self):
735738
self.get_success_response(
736739
self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
737740
)
738-
assert (
739-
call("sentry.issue_alert.conditions.edited", sample_rate=1.0)
740-
in self.metrics.incr.call_args_list
741-
)
742741

743742
def test_edit_non_condition_metric(self):
744743
payload = {
@@ -752,10 +751,6 @@ def test_edit_non_condition_metric(self):
752751
self.get_success_response(
753752
self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
754753
)
755-
assert (
756-
call("sentry.issue_alert.conditions.edited", sample_rate=1.0)
757-
not in self.metrics.incr.call_args_list
758-
)
759754

760755

761756
@region_silo_test(stable=True)

tests/sentry/api/endpoints/test_project_rules.py

+37-3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ def test_simple(self):
5454
class CreateProjectRuleTest(ProjectRuleBaseTestCase):
5555
method = "post"
5656

57+
def clean_data(self, data):
58+
cleaned_data = []
59+
for datum in data:
60+
if datum.get("name"):
61+
del datum["name"]
62+
cleaned_data.append(datum)
63+
return cleaned_data
64+
5765
def run_test(
5866
self,
5967
actions: Sequence[Mapping[str, Any]] | None = None,
@@ -101,9 +109,15 @@ def run_test(
101109
assert rule.owner == get_actor_for_user(self.user)
102110
assert rule.data["action_match"] == action_match
103111
assert rule.data["filter_match"] == filter_match
104-
assert rule.data["actions"] == actions
112+
113+
updated_actions = self.clean_data(actions)
114+
assert rule.data["actions"] == updated_actions
115+
116+
if conditions:
117+
updated_conditions = self.clean_data(conditions)
118+
105119
assert rule.data["conditions"] == (
106-
expected_conditions if expected_conditions is not None else conditions
120+
expected_conditions if expected_conditions is not None else updated_conditions
107121
)
108122
assert rule.data["frequency"] == frequency
109123
assert rule.created_by_id == self.user.id
@@ -127,6 +141,22 @@ def test_simple(self):
127141

128142
self.run_test(actions=actions, conditions=conditions)
129143

144+
def test_with_name(self):
145+
conditions = [
146+
{
147+
"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
148+
"name": "A new issue is created",
149+
}
150+
]
151+
actions = [
152+
{
153+
"id": "sentry.rules.actions.notify_event.NotifyEventAction",
154+
"name": "Send a notification to IssueOwners and if none can be found then send a notification to ActiveMembers",
155+
}
156+
]
157+
158+
self.run_test(actions=actions, conditions=conditions)
159+
130160
def test_with_environment(self):
131161
Environment.get_or_create(self.project, "production")
132162
conditions = [{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}]
@@ -352,7 +382,10 @@ def test_match_values(self):
352382

353383
def test_with_filters(self):
354384
conditions: list[dict[str, Any]] = [
355-
{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}
385+
{
386+
"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
387+
"name": "A new issue is created",
388+
}
356389
]
357390
filters: list[dict[str, Any]] = [
358391
{"id": "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter", "value": 10}
@@ -459,6 +492,7 @@ def test_kicks_off_slack_async_job(
459492

460493
self.user = User.objects.get(id=self.user.id) # reload user to get actor
461494
assert not Rule.objects.filter(label=payload["name"]).exists()
495+
payload["actions"][0].pop("name")
462496
kwargs = {
463497
"name": payload["name"],
464498
"owner": get_actor_id_for_user(self.user),

0 commit comments

Comments
 (0)