Skip to content

Commit

Permalink
feat(workflow): Time To Resolution API (#28910)
Browse files Browse the repository at this point in the history
  • Loading branch information
iProgramStuff authored Oct 1, 2021
1 parent f52c157 commit 197cbb0
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
54 changes: 54 additions & 0 deletions src/sentry/api/endpoints/team_time_to_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from collections import defaultdict
from datetime import timedelta

from django.db.models import Avg, F
from django.db.models.functions import TruncDay
from rest_framework.response import Response

from sentry.api.base import EnvironmentMixin
from sentry.api.bases.team import TeamEndpoint
from sentry.api.utils import get_date_range_from_params
from sentry.models import GroupHistory, GroupHistoryStatus, Project


class TeamTimeToResolutionEndpoint(TeamEndpoint, EnvironmentMixin):
def get(self, request, team):
"""
Return a a time bucketed list of mean group resolution times for a given team.
"""
project_list = Project.objects.get_for_team_ids(team_ids=[team.id])
start, end = get_date_range_from_params(request.GET)
end = end.date() + timedelta(days=1)
start = start.date() + timedelta(days=1)
history_list = (
GroupHistory.objects.filter(
status=GroupHistoryStatus.RESOLVED,
project__in=project_list,
date_added__gte=start,
date_added__lte=end,
)
.annotate(bucket=TruncDay("date_added"))
.values("bucket", "prev_history_date")
.annotate(ttr=F("date_added") - F("prev_history_date"))
.annotate(avg_ttr=Avg("ttr"))
)
sums = defaultdict(lambda: {"sum": timedelta(), "count": 0})
for gh in history_list:
key = str(gh["bucket"].date())
sums[key]["sum"] += gh["ttr"]
sums[key]["count"] += 1

avgs = {}
current_day = start
while current_day < end:
key = str(current_day)
if key in sums:
avg = int((sums[key]["sum"] / sums[key]["count"]).total_seconds())
count = sums[key]["count"]
else:
avg = count = 0

avgs[key] = {"avg": avg, "count": count}
current_day += timedelta(days=1)

return Response(avgs)
6 changes: 6 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
from .endpoints.team_notification_settings_details import TeamNotificationSettingsDetailsEndpoint
from .endpoints.team_projects import TeamProjectsEndpoint
from .endpoints.team_stats import TeamStatsEndpoint
from .endpoints.team_time_to_resolution import TeamTimeToResolutionEndpoint
from .endpoints.user_authenticator_details import UserAuthenticatorDetailsEndpoint
from .endpoints.user_authenticator_enroll import UserAuthenticatorEnrollEndpoint
from .endpoints.user_authenticator_index import UserAuthenticatorIndexEndpoint
Expand Down Expand Up @@ -1453,6 +1454,11 @@
TeamGroupsNewEndpoint.as_view(),
name="sentry-api-0-team-groups-new",
),
url(
r"^(?P<organization_slug>[^\/]+)/(?P<team_slug>[^\/]+)/time-to-resolution/$",
TeamTimeToResolutionEndpoint.as_view(),
name="sentry-api-0-team-time-to-resolution",
),
url(
r"^(?P<organization_slug>[^\/]+)/(?P<team_slug>[^\/]+)/alerts-triggered/$",
TeamAlertsTriggeredEndpoint.as_view(),
Expand Down
111 changes: 111 additions & 0 deletions tests/sentry/api/endpoints/test_team_time_to_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from datetime import timedelta

from django.utils.timezone import now
from freezegun import freeze_time

from sentry.models import GroupHistory, GroupHistoryStatus
from sentry.testutils import APITestCase
from sentry.testutils.helpers.datetime import before_now


@freeze_time()
class TeamTimeToResolutionTest(APITestCase):
endpoint = "sentry-api-0-team-time-to-resolution"

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)

gh1 = GroupHistory.objects.create(
organization=self.organization,
group=group1,
project=project1,
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,
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,
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,
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())
two_days_ago = str((now() - timedelta(days=2)).date())
self.login_as(user=self.user)
response = self.get_success_response(
self.team.organization.slug, self.team.slug, statsPeriod="14d"
)
assert len(response.data) == 14
assert response.data[today]["avg"] == timedelta(days=10).total_seconds()
assert response.data[two_days_ago]["avg"] == timedelta(days=3).total_seconds()
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,
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,
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,
actor=self.user.actor,
status=GroupHistoryStatus.DELETED,
prev_history=gh2,
prev_history_date=gh2.date_added,
)

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[two_days_ago]["avg"] == timedelta(days=3).total_seconds()
assert response.data[yesterday]["avg"] == 0

0 comments on commit 197cbb0

Please sign in to comment.