Skip to content

Commit

Permalink
feat(workflow): Team Alerts Triggered API (#28816)
Browse files Browse the repository at this point in the history
  • Loading branch information
iProgramStuff authored Sep 29, 2021
1 parent 0ae2c82 commit 43d481e
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 1 deletion.
61 changes: 61 additions & 0 deletions src/sentry/api/endpoints/team_alerts_triggered.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from datetime import timedelta

from django.db.models import Count, Q
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.incidents.models import (
IncidentActivity,
IncidentActivityType,
IncidentProject,
IncidentStatus,
)
from sentry.models import Project


class TeamAlertsTriggeredEndpoint(TeamEndpoint, EnvironmentMixin):
def get(self, request, team):
"""
Return a time-bucketed (by day) count of triggered alerts owned by a given team.
"""
project_list = Project.objects.get_for_team_ids([team.id])
owner_ids = [team.actor_id] + list(team.member_set.values_list("user__actor_id", flat=True))
start, end = get_date_range_from_params(request.GET)
bucketed_alert_counts = (
IncidentActivity.objects.filter(
(
Q(type=IncidentActivityType.CREATED.value)
| Q(
type=IncidentActivityType.STATUS_CHANGE.value,
value__in=[
IncidentStatus.OPEN,
IncidentStatus.CRITICAL,
IncidentStatus.WARNING,
],
)
),
incident__alert_rule__owner__in=owner_ids,
incident_id__in=IncidentProject.objects.filter(project__in=project_list).values(
"incident_id"
),
date_added__gte=start,
date_added__lte=end,
)
.annotate(bucket=TruncDay("date_added"))
.values("bucket")
.annotate(count=Count("id"))
)

counts = {str(r["bucket"].replace(tzinfo=None)): r["count"] for r in bucketed_alert_counts}
current_day = start.replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=None
) + timedelta(days=1)
end_day = end.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
while current_day <= end_day:
counts.setdefault(str(current_day), 0)
current_day += timedelta(days=1)

return Response(counts)
6 changes: 6 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@
from .endpoints.shared_group_details import SharedGroupDetailsEndpoint
from .endpoints.system_health import SystemHealthEndpoint
from .endpoints.system_options import SystemOptionsEndpoint
from .endpoints.team_alerts_triggered import TeamAlertsTriggeredEndpoint
from .endpoints.team_avatar import TeamAvatarEndpoint
from .endpoints.team_details import TeamDetailsEndpoint
from .endpoints.team_groups_new import TeamGroupsNewEndpoint
Expand Down Expand Up @@ -1452,6 +1453,11 @@
TeamGroupsNewEndpoint.as_view(),
name="sentry-api-0-team-groups-new",
),
url(
r"^(?P<organization_slug>[^\/]+)/(?P<team_slug>[^\/]+)/alerts-triggered/$",
TeamAlertsTriggeredEndpoint.as_view(),
name="sentry-api-0-team-alerts-triggered",
),
url(
r"^(?P<organization_slug>[^\/]+)/(?P<team_slug>[^\/]+)/(?:issues|groups)/trending/$",
TeamGroupsTrendingEndpoint.as_view(),
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def get_for_user_ids(self, user_ids: Sequence[int]) -> QuerySet:
)

def get_for_team_ids(self, team_ids: Sequence[int]) -> QuerySet:
"""Returns the QuerySet of all organizations that a set of Teams have access to."""
"""Returns the QuerySet of all projects that a set of Teams have access to."""
from sentry.models import ProjectStatus

return self.filter(status=ProjectStatus.VISIBLE, teams__in=team_ids)
Expand Down
181 changes: 181 additions & 0 deletions tests/sentry/api/endpoints/test_team_alerts_triggered.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from freezegun import freeze_time

from sentry.incidents.models import (
AlertRuleThresholdType,
IncidentActivity,
IncidentActivityType,
IncidentStatus,
)
from sentry.models import ActorTuple
from sentry.testutils import APITestCase
from sentry.testutils.helpers.datetime import before_now


@freeze_time()
class TeamAlertsTriggeredTest(APITestCase):
endpoint = "sentry-api-0-team-alerts-triggered"

def test_simple(self):
project1 = self.create_project(
teams=[self.team], slug="foo"
) # This project will return counts for this team
user_owned_rule = self.create_alert_rule(
organization=self.organization,
projects=[project1],
name="user owned rule",
query="",
aggregate="count()",
time_window=1,
threshold_type=AlertRuleThresholdType.ABOVE,
resolve_threshold=10,
threshold_period=1,
owner=ActorTuple.from_actor_identifier(self.user.id),
)
user_owned_incident = self.create_incident(status=20, alert_rule=user_owned_rule)
activities = []
for i in range(1, 9):
activities.append(
IncidentActivity(
incident=user_owned_incident,
type=IncidentActivityType.CREATED.value,
value=IncidentStatus.OPEN,
date_added=before_now(days=i),
)
)
IncidentActivity.objects.bulk_create(activities)

self.login_as(user=self.user)
response = self.get_success_response(self.team.organization.slug, self.team.slug)
assert len(response.data) == 90
for i in range(1, 9):
assert (
response.data[
str(
before_now(days=i).replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=None
)
)
]
== 1
)

for i in range(10, 90):
assert (
response.data[
str(
before_now(days=i).replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=None
)
)
]
== 0
)

response = self.get_success_response(
self.team.organization.slug, self.team.slug, statsPeriod="7d"
)
assert len(response.data) == 7
assert (
response.data[
str(
before_now(days=0).replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=None
)
)
]
== 0
)
for i in range(1, 6):
assert (
response.data[
str(
before_now(days=i).replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=None
)
)
]
== 1
)

def test_not_as_simple(self):
team_with_user = self.create_team(
organization=self.organization, name="Lonely Team", members=[self.user]
)

project1 = self.create_project(
teams=[self.team], slug="foo"
) # This project will return counts for this team
project2 = self.create_project(
# teams=[team_with_user], slug="bar"
teams=[team_with_user],
slug="bar",
) # but not this project, cause this team isn't on it (but the user is)

user_owned_rule = self.create_alert_rule(
organization=self.organization,
projects=[project2],
name="user owned rule",
query="",
aggregate="count()",
time_window=1,
threshold_type=AlertRuleThresholdType.ABOVE,
resolve_threshold=10,
threshold_period=1,
owner=ActorTuple.from_actor_identifier(self.user.id),
)
user_owned_incident = self.create_incident(
projects=[project2], status=20, alert_rule=user_owned_rule
)
team_owned_rule = self.create_alert_rule(
organization=self.organization,
projects=[project1],
name="team owned rule",
query="",
aggregate="count()",
time_window=1,
threshold_type=AlertRuleThresholdType.ABOVE,
resolve_threshold=10,
threshold_period=1,
owner=ActorTuple.from_actor_identifier(f"team:{self.team.id}"),
)
team_owned_incident = self.create_incident(
projects=[project1], status=20, alert_rule=team_owned_rule
)
IncidentActivity.objects.create(
incident=user_owned_incident,
type=IncidentActivityType.CREATED.value,
value=IncidentStatus.OPEN,
)
IncidentActivity.objects.create(
incident=team_owned_incident,
type=IncidentActivityType.CREATED.value,
value=IncidentStatus.OPEN,
date_added=before_now(days=2),
)

self.login_as(user=self.user)
response = self.get_success_response(self.team.organization.slug, self.team.slug)
assert len(response.data) == 90
assert (
response.data[
str(
before_now(days=2).replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=None
)
)
]
== 1
)
# only getting the team owned incident, because the user owned incident is for another project that the team isn't on
for i in range(0, 90):
if i != 2:
assert (
response.data[
str(
before_now(days=i).replace(
hour=0, minute=0, second=0, microsecond=0, tzinfo=None
)
)
]
== 0
)

0 comments on commit 43d481e

Please sign in to comment.