feat: Add silence and unsilence public api endpoint (#5031)

# What this PR does
Exposes alert group silence and unsilence via public api endpoint

## Which issue(s) this PR closes
[#5026](https://github.com/grafana/oncall/issues/5026)

<!--
*Note*: If you want the issue to be auto-closed once the PR is merged,
change "Related to" to "Closes" in the line above.
If you have more than one GitHub issue that this PR closes, be sure to
preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Ravishankar 2024-09-23 18:03:12 +05:30 committed by GitHub
parent 82c564fecb
commit 20d2d5a578
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 173 additions and 0 deletions

View file

@ -130,6 +130,38 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unresolve" \
`POST {{API_URL}}/api/v1/alert_groups/<ALERT_GROUP_ID>/unresolve` `POST {{API_URL}}/api/v1/alert_groups/<ALERT_GROUP_ID>/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/<ALERT_GROUP_ID>/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/<ALERT_GROUP_ID>/unsilence`
## Delete an alert group ## Delete an alert group
```shell ```shell

View file

@ -635,3 +635,98 @@ def test_alert_group_unresolve(
alert_group.refresh_from_db() alert_group.refresh_from_db()
assert alert_group.resolved is False assert alert_group.resolved is False
assert alert_group.log_records.last().action_source == ActionSource.API 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"]

View file

@ -213,3 +213,49 @@ class AlertGroupView(
alert_group.un_resolve_by_user_or_backsync(self.request.user, action_source=ActionSource.API) alert_group.un_resolve_by_user_or_backsync(self.request.user, action_source=ActionSource.API)
return Response(status=status.HTTP_200_OK) 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)