Skip to content

Commit bb72275

Browse files
authored
fix(issues): Fix auto_ongoing_issues task timeouts (#104036)
Fixes: SENTRY-4A6K
1 parent 1928c2c commit bb72275

File tree

2 files changed

+105
-8
lines changed

2 files changed

+105
-8
lines changed

src/sentry/tasks/auto_ongoing_issues.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
from datetime import datetime, timedelta, timezone
33

44
import sentry_sdk
5-
from django.db.models import Max
5+
from django.db.models import Max, OuterRef, Subquery
66

77
from sentry.issues.ongoing import TRANSITION_AFTER_DAYS, bulk_transition_group_to_ongoing
88
from sentry.models.group import Group, GroupStatus
9-
from sentry.models.grouphistory import GroupHistoryStatus
9+
from sentry.models.grouphistory import GroupHistory, GroupHistoryStatus
1010
from sentry.silo.base import SiloMode
1111
from sentry.tasks.base import instrumented_task
1212
from sentry.taskworker.namespaces import issues_tasks
@@ -163,14 +163,28 @@ def get_total_count(results):
163163
nonlocal total_count
164164
total_count += len(results)
165165

166+
date_threshold = datetime.fromtimestamp(date_added_lte, timezone.utc)
167+
168+
# Use a subquery to get the most recent REGRESSED history date for each group.
169+
# This ensures we only transition groups whose MOST RECENT regressed history
170+
# is older than the threshold, not just any regressed history.
171+
latest_regressed_subquery = (
172+
GroupHistory.objects.filter(group_id=OuterRef("id"), status=GroupHistoryStatus.REGRESSED)
173+
.values("group_id")
174+
.annotate(max_date=Max("date_added"))
175+
.values("max_date")[:1]
176+
)
177+
166178
base_queryset = (
167179
Group.objects.filter(
168180
status=GroupStatus.UNRESOLVED,
169181
substatus=GroupSubStatus.REGRESSED,
170-
grouphistory__status=GroupHistoryStatus.REGRESSED,
171182
)
172-
.annotate(recent_regressed_history=Max("grouphistory__date_added"))
173-
.filter(recent_regressed_history__lte=datetime.fromtimestamp(date_added_lte, timezone.utc))
183+
.annotate(recent_regressed_history=Subquery(latest_regressed_subquery))
184+
.filter(
185+
recent_regressed_history__lte=date_threshold,
186+
recent_regressed_history__isnull=False,
187+
)
174188
)
175189

176190
with sentry_sdk.start_span(name="iterate_chunked_group_ids"):
@@ -244,14 +258,30 @@ def get_total_count(results):
244258
nonlocal total_count
245259
total_count += len(results)
246260

261+
from django.db.models import Max, OuterRef, Subquery
262+
263+
date_threshold = datetime.fromtimestamp(date_added_lte, timezone.utc)
264+
265+
# Use a subquery to get the most recent ESCALATING history date for each group.
266+
# This ensures we only transition groups whose MOST RECENT escalating history
267+
# is older than the threshold, not just any escalating history.
268+
latest_escalating_subquery = (
269+
GroupHistory.objects.filter(group_id=OuterRef("id"), status=GroupHistoryStatus.ESCALATING)
270+
.values("group_id")
271+
.annotate(max_date=Max("date_added"))
272+
.values("max_date")[:1]
273+
)
274+
247275
base_queryset = (
248276
Group.objects.filter(
249277
status=GroupStatus.UNRESOLVED,
250278
substatus=GroupSubStatus.ESCALATING,
251-
grouphistory__status=GroupHistoryStatus.ESCALATING,
252279
)
253-
.annotate(recent_escalating_history=Max("grouphistory__date_added"))
254-
.filter(recent_escalating_history__lte=datetime.fromtimestamp(date_added_lte, timezone.utc))
280+
.annotate(recent_escalating_history=Subquery(latest_escalating_subquery))
281+
.filter(
282+
recent_escalating_history__lte=date_threshold,
283+
recent_escalating_history__isnull=False,
284+
)
255285
)
256286

257287
with sentry_sdk.start_span(name="iterate_chunked_group_ids"):

tests/sentry/tasks/test_auto_ongoing_issues.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,73 @@ def test_not_all_groups_get_updated(self, mock_metrics_incr) -> None:
345345
tags={"count": 0},
346346
)
347347

348+
@freeze_time("2023-07-12 18:40:00Z")
349+
def test_only_checks_most_recent_regressed_history(self) -> None:
350+
"""
351+
Test that only the MOST RECENT regressed history is checked against the threshold,
352+
not just any regressed history.
353+
354+
Scenario:
355+
- Group regressed 14 days ago (older than 7-day threshold)
356+
- Group resolved 10 days ago
357+
- Group regressed again 2 days ago (newer than 7-day threshold)
358+
359+
Expected: Group should NOT be transitioned because most recent regression is only 2 days old
360+
"""
361+
now = datetime.now(tz=timezone.utc)
362+
project = self.create_project()
363+
group = self.create_group(
364+
project=project,
365+
status=GroupStatus.UNRESOLVED,
366+
substatus=GroupSubStatus.REGRESSED,
367+
first_seen=now - timedelta(days=14),
368+
)
369+
370+
# Create OLD regressed history (14 days ago) - this is OLDER than threshold
371+
old_regressed_history = record_group_history(
372+
group, GroupHistoryStatus.REGRESSED, actor=None, release=None
373+
)
374+
old_regressed_history.date_added = now - timedelta(days=14)
375+
old_regressed_history.save(update_fields=["date_added"])
376+
377+
# Create resolved history in between (10 days ago)
378+
resolved_history = record_group_history(
379+
group, GroupHistoryStatus.RESOLVED, actor=None, release=None
380+
)
381+
resolved_history.date_added = now - timedelta(days=10)
382+
resolved_history.save(update_fields=["date_added"])
383+
384+
# Create NEW regressed history (2 days ago) - this is NEWER than threshold
385+
# This is the MOST RECENT regressed history
386+
new_regressed_history = record_group_history(
387+
group, GroupHistoryStatus.REGRESSED, actor=None, release=None
388+
)
389+
new_regressed_history.date_added = now - timedelta(days=2)
390+
new_regressed_history.save(update_fields=["date_added"])
391+
392+
# Also create a recent group inbox entry
393+
group_inbox = add_group_to_inbox(group, GroupInboxReason.REGRESSION)
394+
group_inbox.date_added = now - timedelta(days=2)
395+
group_inbox.save(update_fields=["date_added"])
396+
397+
with self.tasks():
398+
schedule_auto_transition_to_ongoing()
399+
400+
# Group should NOT be transitioned because most recent regression is only 2 days old
401+
group.refresh_from_db()
402+
assert group.status == GroupStatus.UNRESOLVED
403+
assert group.substatus == GroupSubStatus.REGRESSED # Should still be REGRESSED
404+
405+
# Should NOT have created an auto-ongoing activity
406+
assert not Activity.objects.filter(
407+
group=group, type=ActivityType.AUTO_SET_ONGOING.value
408+
).exists()
409+
410+
# Should NOT have created an ONGOING history entry
411+
assert not GroupHistory.objects.filter(
412+
group=group, status=GroupHistoryStatus.ONGOING
413+
).exists()
414+
348415

349416
class ScheduleAutoEscalatingOngoingIssuesTest(TestCase):
350417
@freeze_time("2023-07-12 18:40:00Z")

0 commit comments

Comments
 (0)