Skip to content

feat: Snapshot SnubaQueryEventTypes when updating alert rule #94879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/sentry/incidents/endpoints/serializers/incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
)
}

Expand Down
10 changes: 10 additions & 0 deletions src/sentry/incidents/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 14 additions & 3 deletions tests/sentry/incidents/endpoints/serializers/test_incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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})"
67 changes: 67 additions & 0 deletions tests/sentry/incidents/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading