-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(workflow): Team Alerts Triggered API (#28816)
- Loading branch information
1 parent
0ae2c82
commit 43d481e
Showing
4 changed files
with
249 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
181 changes: 181 additions & 0 deletions
181
tests/sentry/api/endpoints/test_team_alerts_triggered.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |