Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow setting direct paging importance for teams #5379

Merged
Merged
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was merged/reviewed in #5382

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:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be done in a separate PR focused on auto-creating 2 routes w/ templates for Direct Paging integrations.

That PR will be merged into this branch, before merging to dev.

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
joeyorlando marked this conversation as resolved.
Show resolved Hide resolved
}'
```

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
Loading