Skip to content

fix(aci): fix rule serializer lastTriggered to account for WorkflowFireHistory #95939

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

Merged
merged 4 commits into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
32 changes: 32 additions & 0 deletions src/sentry/api/serializers/models/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -207,6 +208,37 @@ def get_attrs(self, item_list, user, **kwargs):
.values("rule_id")
.annotate(date_added=Max("date_added"))
}

# Query 2: Get last triggered from WorkflowFireHistory for rules that have workflows
if 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)

Expand Down
53 changes: 52 additions & 1 deletion tests/sentry/api/serializers/test_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
Loading