Add alert groups state filter (#1133)

# What this PR does
This PR added a new parameter (state) into the alert_group public API to
filter the state of the alert groups

## Which issue(s) this PR fixes
https://github.com/grafana/oncall/issues/684

## Checklist

- [x] Tests updated
- [x] Documentation added
- [x] `CHANGELOG.md` updated

Co-authored-by: Vadim Stepanov <vadimkerr@gmail.com>
This commit is contained in:
Tommy 2023-01-17 23:28:29 +13:00 committed by GitHub
parent 268151b0bf
commit 5bd8fbdef8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 4 deletions

View file

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Added state filter for alert_group public API endpoint.
## v1.1.16 (2023-01-12)
### Fixed

View file

@ -46,6 +46,7 @@ These available filter parameters should be provided as `GET` arguments:
- `route_id`
- `integration_id`
- `state`
**HTTP request**

View file

@ -348,6 +348,22 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
# https://code.djangoproject.com/ticket/28545
is_open_for_grouping = models.BooleanField(default=None, null=True, blank=True)
@staticmethod
def get_silenced_state_filter():
return Q(silenced=True) & Q(acknowledged=False) & Q(resolved=False)
@staticmethod
def get_new_state_filter():
return Q(silenced=False) & Q(acknowledged=False) & Q(resolved=False)
@staticmethod
def get_acknowledged_state_filter():
return Q(acknowledged=True) & Q(resolved=False)
@staticmethod
def get_resolved_state_filter():
return Q(resolved=True)
class Meta:
get_latest_by = "pk"
unique_together = [

View file

@ -109,13 +109,13 @@ class AlertGroupFilter(DateRangeFilterMixin, ModelFieldFilterMixin, filters.Filt
q_objects = Q()
if AlertGroup.NEW in statuses:
filters["new"] = Q(silenced=False) & Q(acknowledged=False) & Q(resolved=False)
filters["new"] = AlertGroup.get_new_state_filter()
if AlertGroup.SILENCED in statuses:
filters["silenced"] = Q(silenced=True) & Q(acknowledged=False) & Q(resolved=False)
filters["silenced"] = AlertGroup.get_silenced_state_filter()
if AlertGroup.ACKNOWLEDGED in statuses:
filters["acknowledged"] = Q(acknowledged=True) & Q(resolved=False)
filters["acknowledged"] = AlertGroup.get_acknowledged_state_filter()
if AlertGroup.RESOLVED in statuses:
filters["resolved"] = Q(resolved=True)
filters["resolved"] = AlertGroup.get_resolved_state_filter()
for item in filters:
q_objects |= filters[item]

View file

@ -116,6 +116,83 @@ def test_get_incidents_filter_by_integration(
assert response.json() == expected_response
@pytest.mark.django_db
def test_get_incidents_filter_by_state_new(
incident_public_api_setup,
):
token, _, _, _ = incident_public_api_setup
incidents = AlertGroup.unarchived_objects.filter(AlertGroup.get_new_state_filter()).order_by("-started_at")
expected_response = construct_expected_response_from_incidents(incidents)
client = APIClient()
url = reverse("api-public:alert_groups-list")
response = client.get(url + f"?state=new", format="json", HTTP_AUTHORIZATION=f"{token}")
assert response.status_code == status.HTTP_200_OK
assert response.json() == expected_response
@pytest.mark.django_db
def test_get_incidents_filter_by_state_acknowledged(
incident_public_api_setup,
):
token, _, _, _ = incident_public_api_setup
incidents = AlertGroup.unarchived_objects.filter(AlertGroup.get_acknowledged_state_filter()).order_by("-started_at")
expected_response = construct_expected_response_from_incidents(incidents)
client = APIClient()
url = reverse("api-public:alert_groups-list")
response = client.get(url + f"?state=acknowledged", format="json", HTTP_AUTHORIZATION=f"{token}")
assert response.status_code == status.HTTP_200_OK
assert response.json() == expected_response
@pytest.mark.django_db
def test_get_incidents_filter_by_state_silenced(
incident_public_api_setup,
):
token, _, _, _ = incident_public_api_setup
incidents = AlertGroup.unarchived_objects.filter(AlertGroup.get_silenced_state_filter()).order_by("-started_at")
expected_response = construct_expected_response_from_incidents(incidents)
client = APIClient()
url = reverse("api-public:alert_groups-list")
response = client.get(url + f"?state=silenced", format="json", HTTP_AUTHORIZATION=f"{token}")
assert response.status_code == status.HTTP_200_OK
assert response.json() == expected_response
@pytest.mark.django_db
def test_get_incidents_filter_by_state_resolved(
incident_public_api_setup,
):
token, _, _, _ = incident_public_api_setup
incidents = AlertGroup.unarchived_objects.filter(AlertGroup.get_resolved_state_filter()).order_by("-started_at")
expected_response = construct_expected_response_from_incidents(incidents)
client = APIClient()
url = reverse("api-public:alert_groups-list")
response = client.get(url + f"?state=resolved", format="json", HTTP_AUTHORIZATION=f"{token}")
assert response.status_code == status.HTTP_200_OK
assert response.json() == expected_response
@pytest.mark.django_db
def test_get_incidents_filter_by_state_unknown(
incident_public_api_setup,
):
token, _, _, _ = incident_public_api_setup
client = APIClient()
url = reverse("api-public:alert_groups-list")
response = client.get(url + f"?state=unknown", format="json", HTTP_AUTHORIZATION=f"{token}")
assert response.status_code == status.HTTP_400_BAD_REQUEST
@pytest.mark.django_db
def test_get_incidents_filter_by_integration_no_result(
incident_public_api_setup,

View file

@ -1,3 +1,4 @@
from django.db.models import Q
from django_filters import rest_framework as filters
from rest_framework import mixins, status
from rest_framework.exceptions import NotFound
@ -45,6 +46,7 @@ class IncidentView(RateLimitHeadersMixin, mixins.ListModelMixin, mixins.DestroyM
def get_queryset(self):
route_id = self.request.query_params.get("route_id", None)
integration_id = self.request.query_params.get("integration_id", None)
state = self.request.query_params.get("state", None)
queryset = AlertGroup.unarchived_objects.filter(
channel__organization=self.request.auth.organization,
@ -54,6 +56,25 @@ class IncidentView(RateLimitHeadersMixin, mixins.ListModelMixin, mixins.DestroyM
queryset = queryset.filter(channel_filter__public_primary_key=route_id)
if integration_id:
queryset = queryset.filter(channel__public_primary_key=integration_id)
if state:
choices = dict(AlertGroup.STATUS_CHOICES)
try:
choice = [i for i in choices if choices[i] == state.lower().capitalize()][0]
status_filter = Q()
if choice == AlertGroup.NEW:
status_filter = AlertGroup.get_new_state_filter()
elif choice == AlertGroup.SILENCED:
status_filter = AlertGroup.get_silenced_state_filter()
elif choice == AlertGroup.ACKNOWLEDGED:
status_filter = AlertGroup.get_acknowledged_state_filter()
elif choice == AlertGroup.RESOLVED:
status_filter = AlertGroup.get_resolved_state_filter()
queryset = queryset.filter(status_filter)
except IndexError:
valid_choices_text = ", ".join(
[status_choice[1].lower() for status_choice in AlertGroup.STATUS_CHOICES]
)
raise BadRequest(detail={"state": f"Must be one of the following: {valid_choices_text}"})
queryset = self.serializer_class.setup_eager_loading(queryset)