diff --git a/src/sentry/api/endpoints/team_time_to_resolution.py b/src/sentry/api/endpoints/team_time_to_resolution.py index b3613b7c4517f0..92c17497f17274 100644 --- a/src/sentry/api/endpoints/team_time_to_resolution.py +++ b/src/sentry/api/endpoints/team_time_to_resolution.py @@ -2,7 +2,7 @@ from datetime import timedelta from django.db.models import Avg, F -from django.db.models.functions import TruncDay +from django.db.models.functions import Coalesce, TruncDay from rest_framework.response import Response from sentry.api.base import EnvironmentMixin @@ -29,16 +29,17 @@ def get(self, request, team): ) .annotate(bucket=TruncDay("date_added")) .values("bucket", "prev_history_date") - .annotate(ttr=F("date_added") - F("prev_history_date")) + # We do the coalesce here to handle historical data. At some point every `RESOLVED` row + # will have a non-null `prev_history_date`, and at that point we could remove this. + .annotate( + ttr=F("date_added") - Coalesce(F("prev_history_date"), F("group__first_seen")) + ) .annotate(avg_ttr=Avg("ttr")) ) sums = defaultdict(lambda: {"sum": timedelta(), "count": 0}) for gh in history_list: key = str(gh["bucket"].date()) - if gh["ttr"] is not None: - # If a `GroupHistory` row has no `prev_history_date` then this will end up being - # None. Isn't a problem long term, but for older data we will be missing this value. - sums[key]["sum"] += gh["ttr"] + sums[key]["sum"] += gh["ttr"] sums[key]["count"] += 1 avgs = {} diff --git a/src/sentry/testutils/factories.py b/src/sentry/testutils/factories.py index 0d21996a110e66..b8a67797158107 100644 --- a/src/sentry/testutils/factories.py +++ b/src/sentry/testutils/factories.py @@ -1092,10 +1092,15 @@ def create_group_history( release: Optional[Release] = None, actor: Actor = None, prev_history: GroupHistory = None, + date_added: datetime = None, ) -> GroupHistory: prev_history_date = None if prev_history: prev_history_date = prev_history.date_added + + kwargs = {} + if date_added: + kwargs["date_added"] = date_added return GroupHistory.objects.create( organization=group.organization, group=group, @@ -1105,4 +1110,5 @@ def create_group_history( status=status, prev_history=prev_history, prev_history_date=prev_history_date, + **kwargs, ) diff --git a/tests/sentry/api/endpoints/test_team_time_to_resolution.py b/tests/sentry/api/endpoints/test_team_time_to_resolution.py index bf550554ba46be..eeaaccbb8b6aae 100644 --- a/tests/sentry/api/endpoints/test_team_time_to_resolution.py +++ b/tests/sentry/api/endpoints/test_team_time_to_resolution.py @@ -3,7 +3,7 @@ from django.utils.timezone import now from freezegun import freeze_time -from sentry.models import GroupHistory, GroupHistoryStatus +from sentry.models import GroupHistoryStatus from sentry.testutils import APITestCase from sentry.testutils.helpers.datetime import before_now @@ -16,49 +16,37 @@ def test_simple(self): project1 = self.create_project(teams=[self.team], slug="foo") project2 = self.create_project(teams=[self.team], slug="bar") group1 = self.create_group(checksum="a" * 32, project=project1, times_seen=10) - group2 = self.create_group(checksum="b" * 32, project=project2, times_seen=5) + group2 = self.create_group( + checksum="b" * 32, project=project2, times_seen=5, first_seen=before_now(days=20) + ) - gh1 = GroupHistory.objects.create( - organization=self.organization, - group=group1, - project=project1, + gh1 = self.create_group_history( + group1, + GroupHistoryStatus.UNRESOLVED, actor=self.user.actor, date_added=before_now(days=5), - status=GroupHistoryStatus.UNRESOLVED, - prev_history=None, - prev_history_date=None, ) - GroupHistory.objects.create( - organization=self.organization, - group=group1, - project=project1, + self.create_group_history( + group1, + GroupHistoryStatus.RESOLVED, actor=self.user.actor, - status=GroupHistoryStatus.RESOLVED, prev_history=gh1, - prev_history_date=gh1.date_added, date_added=before_now(days=2), ) - gh2 = GroupHistory.objects.create( - organization=self.organization, - group=group2, - project=project2, + gh2 = self.create_group_history( + group2, + GroupHistoryStatus.UNRESOLVED, actor=self.user.actor, date_added=before_now(days=10), - status=GroupHistoryStatus.UNRESOLVED, - prev_history=None, - prev_history_date=None, ) - GroupHistory.objects.create( - organization=self.organization, - group=group2, - project=project2, + self.create_group_history( + group2, + GroupHistoryStatus.RESOLVED, actor=self.user.actor, - status=GroupHistoryStatus.RESOLVED, prev_history=gh2, - prev_history_date=gh2.date_added, ) today = str(now().date()) yesterday = str((now() - timedelta(days=1)).date()) @@ -73,49 +61,35 @@ def test_simple(self): assert response.data[yesterday]["avg"] == 0 # Lower "todays" average by adding another resolution, but this time 5 days instead of 10 (avg is 7.5 now) - gh2 = GroupHistory.objects.create( - organization=self.organization, - group=group2, - project=project2, + gh2 = self.create_group_history( + group2, + GroupHistoryStatus.UNRESOLVED, actor=self.user.actor, date_added=before_now(days=5), - status=GroupHistoryStatus.UNRESOLVED, - prev_history=None, - prev_history_date=None, ) - GroupHistory.objects.create( - organization=self.organization, - group=group2, - project=project2, + self.create_group_history( + group2, + GroupHistoryStatus.RESOLVED, actor=self.user.actor, - status=GroupHistoryStatus.RESOLVED, prev_history=gh2, - prev_history_date=gh2.date_added, ) # making sure it doesnt bork anything - GroupHistory.objects.create( - organization=self.organization, - group=group2, - project=project2, + self.create_group_history( + group2, + GroupHistoryStatus.DELETED, actor=self.user.actor, - status=GroupHistoryStatus.DELETED, prev_history=gh2, - prev_history_date=gh2.date_added, ) # Make sure that if we have a `GroupHistory` row with no prev history then we don't crash. - GroupHistory.objects.create( - organization=self.organization, - group=group2, - project=project2, + self.create_group_history( + group2, + GroupHistoryStatus.RESOLVED, actor=self.user.actor, - status=GroupHistoryStatus.DELETED, - prev_history=None, - prev_history_date=None, ) response = self.get_success_response(self.team.organization.slug, self.team.slug) assert len(response.data) == 90 - assert response.data[today]["avg"] == timedelta(days=7, hours=12).total_seconds() + assert response.data[today]["avg"] == timedelta(days=11, hours=16).total_seconds() assert response.data[two_days_ago]["avg"] == timedelta(days=3).total_seconds() assert response.data[yesterday]["avg"] == 0