diff --git a/docs/sources/oncall-api-reference/alertgroups.md b/docs/sources/oncall-api-reference/alertgroups.md index bb8d6f66..9b9d007f 100644 --- a/docs/sources/oncall-api-reference/alertgroups.md +++ b/docs/sources/oncall-api-reference/alertgroups.md @@ -130,6 +130,38 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unresolve" \ `POST {{API_URL}}/api/v1/alert_groups//unresolve` +## Silence an alert group + +```shell +curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/silence" \ + --request POST \ + --header "Authorization: meowmeowmeow" \ + --header "Content-Type: application/json" \ + --data '{ + "delay": 10800 + }' +``` + +**HTTP request** + +`POST {{API_URL}}/api/v1/alert_groups//silence` + +| Parameter | Required | Description | +|-----------|:--------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `delay` | Yes | The duration of silence in seconds, `-1` for silencing the alert forever | + +## Unsilence an alert group + +```shell +curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unsilence" \ + --request POST \ + --header "Authorization: meowmeowmeow" +``` + +**HTTP request** + +`POST {{API_URL}}/api/v1/alert_groups//unsilence` + ## Delete an alert group ```shell diff --git a/engine/apps/public_api/tests/test_alert_groups.py b/engine/apps/public_api/tests/test_alert_groups.py index 3cfc9c85..758b0d99 100644 --- a/engine/apps/public_api/tests/test_alert_groups.py +++ b/engine/apps/public_api/tests/test_alert_groups.py @@ -635,3 +635,98 @@ def test_alert_group_unresolve( alert_group.refresh_from_db() assert alert_group.resolved is False assert alert_group.log_records.last().action_source == ActionSource.API + + +@pytest.mark.parametrize( + "acknowledged,resolved,attached,status_code,data,response_msg", + [ + (False, False, False, status.HTTP_200_OK, {"delay": 60}, None), + (False, False, False, status.HTTP_400_BAD_REQUEST, {"delay": -2}, "invalid delay value"), + (False, False, False, status.HTTP_400_BAD_REQUEST, {"delay": "fuzz"}, "invalid delay value"), + (False, False, False, status.HTTP_400_BAD_REQUEST, {}, "delay is required"), + (True, False, False, status.HTTP_400_BAD_REQUEST, {"delay": 60}, "Can't silence an acknowledged alert group"), + (False, True, False, status.HTTP_400_BAD_REQUEST, {"delay": 60}, "Can't silence a resolved alert group"), + (False, False, True, status.HTTP_400_BAD_REQUEST, {"delay": 60}, "Can't silence an attached alert group"), + ], +) +@pytest.mark.django_db +def test_alert_group_silence( + make_organization_and_user_with_token, + make_alert_receive_channel, + make_alert_group, + acknowledged, + resolved, + attached, + status_code, + data, + response_msg, +): + organization, _, token = make_organization_and_user_with_token() + alert_receive_channel = make_alert_receive_channel(organization) + root_alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group( + alert_receive_channel, + acknowledged=acknowledged, + resolved=resolved, + root_alert_group=root_alert_group if attached else None, + ) + + client = APIClient() + url = reverse("api-public:alert_groups-silence", kwargs={"pk": alert_group.public_primary_key}) + response = client.post(url, data=data, HTTP_AUTHORIZATION=token) + + if status_code == status.HTTP_200_OK: + alert_group.refresh_from_db() + assert alert_group.silenced is True + assert alert_group.log_records.last().action_source == ActionSource.API + else: + assert alert_group.silenced is False + assert response.status_code == status_code + assert response_msg == response.json()["detail"] + + +@pytest.mark.parametrize( + "silenced,resolved,acknowledged,attached,status_code,response_msg", + [ + (True, False, False, False, status.HTTP_200_OK, None), + (False, False, False, False, status.HTTP_400_BAD_REQUEST, "Can't unsilence an unsilenced alert group"), + (True, True, False, False, status.HTTP_400_BAD_REQUEST, "Can't unsilence a resolved alert group"), + (True, False, True, False, status.HTTP_400_BAD_REQUEST, "Can't unsilence an acknowledged alert group"), + (True, False, False, True, status.HTTP_400_BAD_REQUEST, "Can't unsilence an attached alert group"), + ], +) +@pytest.mark.django_db +def test_alert_group_unsilence( + make_organization_and_user_with_token, + make_alert_receive_channel, + make_alert_group, + silenced, + resolved, + acknowledged, + attached, + status_code, + response_msg, +): + organization, _, token = make_organization_and_user_with_token() + alert_receive_channel = make_alert_receive_channel(organization) + root_alert_group = make_alert_group(alert_receive_channel) + alert_group = make_alert_group( + alert_receive_channel, + acknowledged=acknowledged, + resolved=resolved, + silenced=silenced, + root_alert_group=root_alert_group if attached else None, + ) + + client = APIClient() + url = reverse("api-public:alert_groups-unsilence", kwargs={"pk": alert_group.public_primary_key}) + response = client.post(url, HTTP_AUTHORIZATION=token) + + if status_code == status.HTTP_200_OK: + alert_group.refresh_from_db() + assert alert_group.silenced is False + assert alert_group.log_records.last().action_source == ActionSource.API + else: + assert alert_group.silenced == silenced + assert response.status_code == status_code + assert response_msg == response.json()["detail"] diff --git a/engine/apps/public_api/views/alert_groups.py b/engine/apps/public_api/views/alert_groups.py index 0db6f349..c1b5fe1d 100644 --- a/engine/apps/public_api/views/alert_groups.py +++ b/engine/apps/public_api/views/alert_groups.py @@ -213,3 +213,49 @@ class AlertGroupView( alert_group.un_resolve_by_user_or_backsync(self.request.user, action_source=ActionSource.API) return Response(status=status.HTTP_200_OK) + + @action(methods=["post"], detail=True) + def silence(self, request, pk=None): + alert_group = self.get_object() + + delay = request.data.get("delay") + if delay is None: + raise BadRequest(detail="delay is required") + try: + delay = int(delay) + except ValueError: + raise BadRequest(detail="invalid delay value") + if delay < -1: + raise BadRequest(detail="invalid delay value") + + if alert_group.resolved: + raise BadRequest(detail="Can't silence a resolved alert group") + + if alert_group.acknowledged: + raise BadRequest(detail="Can't silence an acknowledged alert group") + + if alert_group.root_alert_group is not None: + raise BadRequest(detail="Can't silence an attached alert group") + + alert_group.silence_by_user_or_backsync(request.user, silence_delay=delay, action_source=ActionSource.API) + return Response(status=status.HTTP_200_OK) + + @action(methods=["post"], detail=True) + def unsilence(self, request, pk=None): + alert_group = self.get_object() + + if not alert_group.silenced: + raise BadRequest(detail="Can't unsilence an unsilenced alert group") + + if alert_group.resolved: + raise BadRequest(detail="Can't unsilence a resolved alert group") + + if alert_group.acknowledged: + raise BadRequest(detail="Can't unsilence an acknowledged alert group") + + if alert_group.root_alert_group is not None: + raise BadRequest(detail="Can't unsilence an attached alert group") + + alert_group.un_silence_by_user_or_backsync(request.user, action_source=ActionSource.API) + + return Response(status=status.HTTP_200_OK)