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:
parent
82c564fecb
commit
20d2d5a578
3 changed files with 173 additions and 0 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue