Skip to content

Commit

Permalink
feat: allow setting importance when direct paging a team
Browse files Browse the repository at this point in the history
  • Loading branch information
joeyorlando committed Dec 19, 2024
1 parent a3459a7 commit 7efb55c
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,7 @@ Navigate to the **Integrations** page and find the "Direct paging" integration f
integration's detail page, you can customize its settings, link it to an escalation chain, and configure associated
ChatOps channels. To confirm that the integration is functioning as intended, [create a new alert group](#page-a-team)
and select the same team for a test run.

### Important escalations

TODO:
13 changes: 12 additions & 1 deletion docs/sources/oncall-api-reference/escalation.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ refs:
destination: /docs/oncall/<ONCALL_VERSION>/configure/integrations/references/manual
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/configure/integrations/references/manual
manual-paging-team-important:
- pattern: /docs/oncall/
destination: /docs/oncall/<ONCALL_VERSION>/configure/integrations/references/manual#important-escalations
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/configure/integrations/references/manual#important-escalations
---

# Escalation HTTP API
Expand Down Expand Up @@ -90,7 +95,8 @@ curl "{{API_URL}}/api/v1/escalation/" \
"title": "We are seeing a network outage in the datacenter",
"message": "I need help investigating, can you join the investigation?",
"source_url": "https://github.com/myorg/myrepo/issues/123",
"team": "TI73TDU19W48J"
"team": "TI73TDU19W48J",
"important_team_escalation": True
}'
```

Expand Down Expand Up @@ -176,6 +182,7 @@ The above command returns JSON structured in the following way:
| `team` | No | Yes (see [Things to Note](#things-to-note)) | Grafana OnCall team ID. If specified, will use the "Direct Paging" Integration associated with this Grafana OnCall team, to create the Alert Group. |
| `users` | No | Yes (see [Things to Note](#things-to-note)) | List of user(s) to escalate to. See above request example for object schema. `id` represents the Grafana OnCall user's ID. `important` is a boolean representing whether to escalate the Alert Group using this user's default or important personal notification policy. |
| `alert_group_id` | No | No | If specified, will escalate the specified users for this Alert Group. |
| `important_team_escalation` | No | No | Sets the value of `payload.oncall.important` to the value specified here (default is `False`; see [Things to Note](#things-to-note) for more details). |

## Things to note

Expand All @@ -186,6 +193,10 @@ existing Alert Group
if you are trying to escalate to a set of users on an existing Alert Group, you cannot update the `title`, `message`, or
`source_url` of that Alert Group
- If escalating to a set of users for an existing Alert Group, the Alert Group cannot be in a resolved state
- Regarding `important_team_escalation`; this can be useful to send an "important" escalation to the specified team.
Teams can configure their Direct Paging Integration to route to different escalation chains based on the value of
`payload.oncall.important`. See [Manual paging integration - important escalations](ref:manual-paging-team-important)
for more details.

**HTTP request**

Expand Down
20 changes: 19 additions & 1 deletion engine/apps/alerts/paging.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class DirectPagingAlertPayload(typing.TypedDict):
def _trigger_alert(
organization: Organization,
team: Team | None,
important_team_escalation: bool,
message: str,
title: str,
permalink: str | None,
Expand Down Expand Up @@ -82,6 +83,13 @@ def _trigger_alert(
"uid": str(uuid4()), # avoid grouping
"author_username": from_user.username,
"permalink": permalink,
# NOTE: this field is mostly being added for purposes of escalating to a team
# this field is provided via the web UI/API/slack as a checkbox, indicating that the user doing the paging
# would like to send an "important" page to the team.
#
# Teams can configure routing in their Direct Paging Integration to route based on this field to different
# escalation chains
"important": important_team_escalation,
},
}

Expand Down Expand Up @@ -128,6 +136,7 @@ def direct_paging(
source_url: str | None = None,
grafana_incident_id: str | None = None,
team: Team | None = None,
important_team_escalation: bool = False,
users: UserNotifications | None = None,
alert_group: AlertGroup | None = None,
) -> AlertGroup | None:
Expand Down Expand Up @@ -156,7 +165,16 @@ def direct_paging(
# create alert group if needed
with transaction.atomic():
if alert_group is None:
alert_group = _trigger_alert(organization, team, message, title, source_url, grafana_incident_id, from_user)
alert_group = _trigger_alert(
organization,
team,
important_team_escalation,
message,
title,
source_url,
grafana_incident_id,
from_user,
)

for u, important in users:
alert_group.log_records.create(
Expand Down
31 changes: 27 additions & 4 deletions engine/apps/alerts/tests/test_paging.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import call, patch
from unittest.mock import ANY, call, patch

import pytest
from django.utils import timezone
Expand Down Expand Up @@ -86,23 +86,46 @@ def test_direct_paging_user(make_organization, make_user_for_organization, djang
assert_log_record(ag, f"{from_user.username} paged user {u.username}", expected_info=expected_info)


@pytest.mark.parametrize("important_team_escalation", [True, False])
@pytest.mark.django_db
def test_direct_paging_team(make_organization, make_team, make_user_for_organization):
def test_direct_paging_team(make_organization, make_team, make_user_for_organization, important_team_escalation):
organization = make_organization()
from_user = make_user_for_organization(organization)
team = make_team(organization)

from_author_username = from_user.username
source_url = "https://www.example.com"
title = f"{from_author_username} is paging {team.name} to join escalation"
msg = "Fire"

direct_paging(organization, from_user, msg, team=team)
direct_paging(
organization,
from_user,
msg,
source_url=source_url,
team=team,
important_team_escalation=important_team_escalation,
)

# alert group created
alert_groups = AlertGroup.objects.all()
assert alert_groups.count() == 1
ag = alert_groups.get()
alert = ag.alerts.get()
assert alert.title == f"{from_user.username} is paging {team.name} to join escalation"
assert alert.title == title
assert alert.message == msg

assert alert.raw_request_data == {
"oncall": {
"title": title,
"message": msg,
"uid": ANY,
"author_username": from_author_username,
"permalink": source_url,
"important": important_team_escalation,
},
}

assert ag.channel.verbal_name == f"Direct paging ({team.name} team)"
assert ag.channel.team == team

Expand Down
1 change: 1 addition & 0 deletions engine/apps/api/serializers/direct_paging.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class BasePagingSerializer(serializers.Serializer):

users = UserReferenceSerializer(many=True, required=False, default=list)
team = TeamPrimaryKeyRelatedField(allow_null=True, default=CurrentTeamDefault())
important_team_escalation = serializers.BooleanField(required=False, default=False)

alert_group_id = serializers.CharField(required=False, default=None)
alert_group = serializers.HiddenField(default=None) # set in DirectPagingSerializer.validate
Expand Down
16 changes: 15 additions & 1 deletion engine/apps/api/tests/test_direct_paging.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import ANY

import pytest
from django.urls import reverse
from rest_framework import status
Expand Down Expand Up @@ -59,11 +61,13 @@ def test_direct_paging_new_alert_group(
assert alert.message == message


@pytest.mark.parametrize("important_team_escalation", [True, False])
@pytest.mark.django_db
def test_direct_paging_page_team(
make_organization_and_user_with_plugin_token,
make_team,
make_user_auth_headers,
important_team_escalation,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
team = make_team(organization=organization)
Expand All @@ -81,6 +85,7 @@ def test_direct_paging_page_team(
"message": message,
"source_url": source_url,
"grafana_incident_id": grafana_incident_id,
"important_team_escalation": important_team_escalation,
},
format="json",
**make_user_auth_headers(user, token),
Expand All @@ -92,7 +97,16 @@ def test_direct_paging_page_team(
alert = alert_group.alerts.first()

assert alert_group.grafana_incident_id == grafana_incident_id
assert alert.raw_request_data["oncall"]["permalink"] == source_url
assert alert.raw_request_data == {
"oncall": {
"title": ANY,
"message": message,
"uid": ANY,
"author_username": ANY,
"permalink": source_url,
"important": important_team_escalation,
},
}


@pytest.mark.django_db
Expand Down
1 change: 1 addition & 0 deletions engine/apps/api/views/direct_paging.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def post(self, request):
source_url=validated_data["source_url"],
grafana_incident_id=validated_data["grafana_incident_id"],
team=validated_data["team"],
important_team_escalation=validated_data["important_team_escalation"],
users=[(user["instance"], user["important"]) for user in validated_data["users"]],
alert_group=validated_data["alert_group"],
)
Expand Down
14 changes: 13 additions & 1 deletion engine/apps/public_api/tests/test_escalation.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ def test_escalation_new_alert_group(
assert alert.message == message


@pytest.mark.parametrize("important_team_escalation", [True, False])
@pytest.mark.django_db
def test_escalation_team(
make_organization_and_user_with_token,
make_team,
make_user_auth_headers,
important_team_escalation,
):
organization, user, token = make_organization_and_user_with_token()
team = make_team(organization=organization)
Expand All @@ -110,6 +112,7 @@ def test_escalation_team(
"team": team.public_primary_key,
"message": message,
"source_url": source_url,
"important_team_escalation": important_team_escalation,
},
format="json",
**make_user_auth_headers(user, token),
Expand All @@ -120,7 +123,16 @@ def test_escalation_team(
alert_group = AlertGroup.objects.get(public_primary_key=response.json()["id"])
alert = alert_group.alerts.first()

assert alert.raw_request_data["oncall"]["permalink"] == source_url
assert alert.raw_request_data == {
"oncall": {
"title": mock.ANY,
"message": message,
"uid": mock.ANY,
"author_username": mock.ANY,
"permalink": source_url,
"important": important_team_escalation,
},
}


@pytest.mark.django_db
Expand Down
1 change: 1 addition & 0 deletions engine/apps/public_api/views/escalation.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def post(self, request):
title=validated_data["title"],
source_url=validated_data["source_url"],
team=validated_data["team"],
important_team_escalation=validated_data["important_team_escalation"],
users=[(user["instance"], user["important"]) for user in validated_data["users"]],
alert_group=validated_data["alert_group"],
)
Expand Down
Loading

0 comments on commit 7efb55c

Please sign in to comment.