diff --git a/src/sentry/api/serializers/models/rule.py b/src/sentry/api/serializers/models/rule.py index 12727d7f634099..95c4edd20f30ee 100644 --- a/src/sentry/api/serializers/models/rule.py +++ b/src/sentry/api/serializers/models/rule.py @@ -9,6 +9,7 @@ from django.db.models import Max, Prefetch, Q, prefetch_related_objects from rest_framework import serializers +from sentry import features from sentry.api.serializers import Serializer, register from sentry.constants import ObjectStatus from sentry.db.models.manager.base_query_set import BaseQuerySet @@ -207,6 +208,37 @@ def get_attrs(self, item_list, user, **kwargs): .values("rule_id") .annotate(date_added=Max("date_added")) } + + # Update lastTriggered with WorkflowFireHistory if available + if item_list and features.has( + "organizations:workflow-engine-single-process-workflows", + item_list[0].project.organization, + ): + rule_ids = [rule.id for rule in item_list] + workflow_rule_lookup = dict( + AlertRuleWorkflow.objects.filter(rule_id__in=rule_ids).values_list( + "workflow_id", "rule_id" + ) + ) + + workflow_fire_results = ( + WorkflowFireHistory.objects.filter( + workflow_id__in=workflow_rule_lookup.keys(), is_single_written=True + ) + .values("workflow_id") + .annotate(date_added=Max("date_added")) + ) + + for wfh in workflow_fire_results: + rule_id = workflow_rule_lookup.get(wfh["workflow_id"]) + if rule_id: + # Take the maximum date between RuleFireHistory and WorkflowFireHistory + existing_date = last_triggered_lookup[rule_id] + new_date = wfh["date_added"] + if new_date > existing_date: + last_triggered_lookup[rule_id] = new_date + + # Set the results for rule in item_list: result[rule]["last_triggered"] = last_triggered_lookup.get(rule.id, None) diff --git a/tests/sentry/api/serializers/test_rule.py b/tests/sentry/api/serializers/test_rule.py index 247efb26e52a12..d32083489ac879 100644 --- a/tests/sentry/api/serializers/test_rule.py +++ b/tests/sentry/api/serializers/test_rule.py @@ -3,7 +3,7 @@ from django.utils import timezone from sentry.api.serializers import serialize -from sentry.api.serializers.models.rule import WorkflowEngineRuleSerializer +from sentry.api.serializers.models.rule import RuleSerializer, WorkflowEngineRuleSerializer from sentry.models.rulefirehistory import RuleFireHistory from sentry.rules.conditions.event_frequency import EventUniqueUserFrequencyConditionWithConditions from sentry.rules.conditions.reappeared_event import ReappearedEventCondition @@ -14,12 +14,63 @@ from sentry.rules.filters.tagged_event import TaggedEventFilter from sentry.testutils.cases import TestCase from sentry.testutils.helpers.datetime import before_now, freeze_time +from sentry.testutils.helpers.features import with_feature from sentry.users.services.user.serial import serialize_rpc_user from sentry.workflow_engine.migration_helpers.issue_alert_migration import IssueAlertMigrator from sentry.workflow_engine.models import WorkflowDataConditionGroup, WorkflowFireHistory from sentry.workflow_engine.models.data_condition import Condition +@freeze_time() +class RuleSerializerTest(TestCase): + def test_last_triggered_rule_only(self): + rule = self.create_project_rule() + + # Initially no fire history + result = serialize(rule, self.user, RuleSerializer(expand=["lastTriggered"])) + assert result["lastTriggered"] is None + + # Create a RuleFireHistory + RuleFireHistory.objects.create(project=self.project, rule=rule, group=self.group) + + result = serialize(rule, self.user, RuleSerializer(expand=["lastTriggered"])) + assert result["lastTriggered"] == timezone.now() + + @with_feature("organizations:workflow-engine-single-process-workflows") + def test_last_triggered_with_workflow(self): + rule = self.create_project_rule() + + # Create a workflow for the rule + workflow = IssueAlertMigrator(rule).run() + + # Create an older RuleFireHistory + rfh = RuleFireHistory.objects.create(project=self.project, rule=rule, group=self.group) + rfh.update(date_added=before_now(hours=2)) + + # Create a newer WorkflowFireHistory + WorkflowFireHistory.objects.create( + workflow=workflow, group=self.group, event_id="test-event-id", is_single_written=True + ) + + result = serialize(rule, self.user, RuleSerializer(expand=["lastTriggered"])) + assert result["lastTriggered"] == timezone.now() + + def test_last_triggered_workflow_ignore_single_written_false(self): + """Test that WorkflowFireHistory with is_single_written=False is ignored.""" + rule = self.create_project_rule() + + # Create a workflow for the rule + workflow = IssueAlertMigrator(rule).run() + + # Create a WorkflowFireHistory with is_single_written=False + WorkflowFireHistory.objects.create( + workflow=workflow, group=self.group, event_id="test-event-id", is_single_written=False + ) + + result = serialize(rule, self.user, RuleSerializer(expand=["lastTriggered"])) + assert result["lastTriggered"] is None + + @freeze_time() class WorkflowRuleSerializerTest(TestCase): def setUp(self):