diff --git a/src/sentry/incidents/endpoints/serializers/incident.py b/src/sentry/incidents/endpoints/serializers/incident.py index 547e8f6e5d97df..03168ca70f384e 100644 --- a/src/sentry/incidents/endpoints/serializers/incident.py +++ b/src/sentry/incidents/endpoints/serializers/incident.py @@ -7,8 +7,8 @@ from sentry.api.serializers import Serializer, register, serialize from sentry.api.serializers.models.incidentactivity import IncidentActivitySerializerResponse from sentry.incidents.endpoints.serializers.alert_rule import ( - AlertRuleSerializer, AlertRuleSerializerResponse, + DetailedAlertRuleSerializer, ) from sentry.incidents.models.incident import Incident, IncidentActivity, IncidentProject from sentry.snuba.entity_subscription import apply_dataset_query_conditions @@ -54,7 +54,7 @@ def get_attrs(self, item_list, user, **kwargs): for d in serialize( {i.alert_rule for i in item_list if i.alert_rule.id}, user, - AlertRuleSerializer(expand=self.expand), + DetailedAlertRuleSerializer(expand=self.expand), ) } diff --git a/src/sentry/incidents/logic.py b/src/sentry/incidents/logic.py index 30eb80ba68be90..3ba2544e7fa832 100644 --- a/src/sentry/incidents/logic.py +++ b/src/sentry/incidents/logic.py @@ -698,6 +698,16 @@ def nullify_id(model: Model) -> None: snuba_query_snapshot: SnubaQuery = deepcopy(_unpack_snuba_query(alert_rule)) nullify_id(snuba_query_snapshot) snuba_query_snapshot.save() + + event_types = SnubaQueryEventType.objects.filter( + snuba_query=_unpack_snuba_query(alert_rule) + ) + for event_type in event_types: + event_type_snapshot = deepcopy(event_type) + nullify_id(event_type_snapshot) + event_type_snapshot.snuba_query = snuba_query_snapshot + event_type_snapshot.save() + alert_rule_snapshot = deepcopy(alert_rule) nullify_id(alert_rule_snapshot) alert_rule_snapshot.status = AlertRuleStatus.SNAPSHOT.value diff --git a/tests/sentry/incidents/endpoints/serializers/test_incident.py b/tests/sentry/incidents/endpoints/serializers/test_incident.py index fad3ba967b7321..87ce4685cc40f0 100644 --- a/tests/sentry/incidents/endpoints/serializers/test_incident.py +++ b/tests/sentry/incidents/endpoints/serializers/test_incident.py @@ -3,6 +3,7 @@ from django.utils import timezone from sentry.api.serializers import serialize +from sentry.incidents.endpoints.serializers.alert_rule import DetailedAlertRuleSerializer from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializer from sentry.snuba.dataset import Dataset from sentry.testutils.cases import TestCase @@ -36,7 +37,10 @@ def test_error_alert_rule(self): serializer = DetailedIncidentSerializer() result = serialize(incident, serializer=serializer) - assert result["alertRule"] == serialize(incident.alert_rule) + alert_rule_serializer = DetailedAlertRuleSerializer() + assert result["alertRule"] == serialize( + incident.alert_rule, serializer=alert_rule_serializer + ) assert result["discoverQuery"] == f"(event.type:error) AND ({query})" def test_error_alert_rule_unicode(self): @@ -45,7 +49,11 @@ def test_error_alert_rule_unicode(self): serializer = DetailedIncidentSerializer() result = serialize(incident, serializer=serializer) - assert result["alertRule"] == serialize(incident.alert_rule) + + alert_rule_serializer = DetailedAlertRuleSerializer() + assert result["alertRule"] == serialize( + incident.alert_rule, serializer=alert_rule_serializer + ) assert result["discoverQuery"] == f"(event.type:error) AND ({query})" def test_transaction_alert_rule(self): @@ -55,5 +63,8 @@ def test_transaction_alert_rule(self): serializer = DetailedIncidentSerializer() result = serialize(incident, serializer=serializer) - assert result["alertRule"] == serialize(incident.alert_rule) + alert_rule_serializer = DetailedAlertRuleSerializer() + assert result["alertRule"] == serialize( + incident.alert_rule, serializer=alert_rule_serializer + ) assert result["discoverQuery"] == f"(event.type:transaction) AND ({query})" diff --git a/tests/sentry/incidents/test_logic.py b/tests/sentry/incidents/test_logic.py index 79a18e2be74032..d98c5352081368 100644 --- a/tests/sentry/incidents/test_logic.py +++ b/tests/sentry/incidents/test_logic.py @@ -1822,6 +1822,73 @@ def test_update_invalid_time_window(self, mock_seer_request): with pytest.raises(ValidationError): update_alert_rule(rule, detection_type=AlertRuleDetectionType.DYNAMIC, time_window=300) + def test_snapshot_alert_rule_with_event_types(self): + # Create alert rule with event types + alert_rule = create_alert_rule( + self.organization, + [self.project], + "test alert rule", + "severity:error", + "count()", + 1, + AlertRuleThresholdType.ABOVE, + 1, + event_types=[SnubaQueryEventType.EventType.TRACE_ITEM_LOG], + query_type=SnubaQuery.Type.PERFORMANCE, + dataset=Dataset.EventsAnalyticsPlatform, + ) + + # Create incident to trigger snapshot + incident = self.create_incident() + incident.update(alert_rule=alert_rule) + + # Verify original event types exist + original_event_types = SnubaQueryEventType.objects.filter( + snuba_query=alert_rule.snuba_query + ) + assert [snuba_event_type.type for snuba_event_type in original_event_types] == [ + SnubaQueryEventType.EventType.TRACE_ITEM_LOG.value + ] + + # Update alert rule to trigger snapshot + with self.tasks(): + updated_rule = update_alert_rule( + alert_rule, + query="level:warning", + event_types=[SnubaQueryEventType.EventType.TRACE_ITEM_SPAN], + ) + + # Find the snapshot + rule_snapshot = ( + AlertRule.objects_with_snapshots.filter(name=alert_rule.name) + .exclude(id=updated_rule.id) + .get() + ) + + # Verify snapshot has its own SnubaQuery + assert rule_snapshot.snuba_query_id != updated_rule.snuba_query_id + + # Verify snapshot has the original event types + snapshot_event_types = SnubaQueryEventType.objects.filter( + snuba_query=rule_snapshot.snuba_query + ) + assert [snuba_event_type.type for snuba_event_type in snapshot_event_types] == [ + SnubaQueryEventType.EventType.TRACE_ITEM_LOG.value + ] + + # Verify event types are different objects but have same values + original_types = {snuba_event_type.type for snuba_event_type in original_event_types} + snapshot_types = {snuba_event_type.type for snuba_event_type in snapshot_event_types} + assert original_types == snapshot_types + + # Verify updated rule has new event types + updated_event_types = SnubaQueryEventType.objects.filter( + snuba_query=updated_rule.snuba_query + ) + assert [snuba_event_type.type for snuba_event_type in updated_event_types] == [ + SnubaQueryEventType.EventType.TRACE_ITEM_SPAN.value + ] + class DeleteAlertRuleTest(TestCase, BaseIncidentsTest): def setUp(self):