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

Set team on Alert Group based on route #3459 #5320

Open
wants to merge 9 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/sources/oncall-api-reference/alertgroups.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,17 @@ The above command returns JSON structured in the following way:
]
}
},
"teams": [
"TE5EF3RQHJQPI"
]
}
],
"current_page_number": 1,
"page_size": 50,
"total_pages": 1
}
```
> **Note**: `team_id` is provided for each alert_group however this is based off the old method where team was assigned based on integration team. It's recommended to use the new `teams` field.

> **Note**: The response is [paginated](ref:pagination). You may need to make multiple requests to get all records.

Expand Down
19 changes: 19 additions & 0 deletions engine/apps/alerts/migrations/0065_alertgroup_teams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.2.16 on 2024-11-19 06:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('user_management', '0026_auto_20241017_1919'),
('alerts', '0064_migrate_resolutionnoteslackmessage_slack_channel_id'),
]

operations = [
migrations.AddField(
model_name='alertgroup',
name='teams',
field=models.ManyToManyField(to='user_management.team'),
),
]
18 changes: 18 additions & 0 deletions engine/apps/alerts/migrations/0066_channelfilter_update_team.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-11-22 00:22

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('alerts', '0065_alertgroup_teams'),
]

operations = [
migrations.AddField(
model_name='channelfilter',
name='update_team',
field=models.BooleanField(default=False, null=True),
),
]
12 changes: 12 additions & 0 deletions engine/apps/alerts/models/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ def create(
group.log_records.create(type=AlertGroupLogRecord.TYPE_REGISTERED)
group.log_records.create(type=AlertGroupLogRecord.TYPE_ROUTE_ASSIGNED)

if alert_receive_channel.team:
# add the team from the integration if its available
group.teams.set([alert_receive_channel.team])
elif (
channel_filter
and channel_filter.escalation_chain
and channel_filter.escalation_chain.team
and channel_filter.update_team
):
# set the team to the one defined in the escalation_chain if defined.
group.teams.set([channel_filter.escalation_chain.team])

if group_created or alert.group.pause_escalation:
# Build escalation snapshot if needed and start escalation
alert.group.start_escalation_if_needed(countdown=TASK_DELAY_SECONDS)
Expand Down
2 changes: 2 additions & 0 deletions engine/apps/alerts/models/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ def status(self) -> int:

raw_escalation_snapshot = JSONField(null=True, default=None)

teams = models.ManyToManyField(to="user_management.Team")

# This field is used for constraints so we can use get_or_create() in concurrent calls
# https://docs.djangoproject.com/en/3.2/ref/models/querysets/#get-or-create
# Combined with unique_together below, it allows only one alert group with
Expand Down
3 changes: 3 additions & 0 deletions engine/apps/alerts/models/channel_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class ChannelFilter(OrderedModel):
"alerts.EscalationChain", null=True, default=None, on_delete=models.SET_NULL, related_name="channel_filters"
)

# Should we update the alertgroup team when this route is used
update_team = models.BooleanField(null=True, default=False)

notify_in_slack = models.BooleanField(null=True, default=True)
notify_in_telegram = models.BooleanField(null=True, default=False)
slack_channel = models.ForeignKey(
Expand Down
97 changes: 96 additions & 1 deletion engine/apps/alerts/tests/test_channel_filter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from apps.alerts.models import ChannelFilter
from apps.alerts.models import ChannelFilter, Alert


@pytest.mark.django_db
Expand Down Expand Up @@ -216,3 +216,98 @@ def test_slack_channel_or_org_default_none(

# Assert that slack_channel_or_org_default returns None
assert channel_filter.slack_channel_or_org_default is None
@pytest.mark.django_db
def test_channel_filter_team_update(
make_organization_and_user_with_plugin_token,
make_team,
make_escalation_chain,
make_alert_receive_channel,
make_channel_filter,
):
organization, user, token = make_organization_and_user_with_plugin_token()
team = make_team(organization)

alert_receive_channel = make_alert_receive_channel(organization)

escalation_chain = make_escalation_chain(organization=organization)
escalation_chain.team = team

channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain, is_default=True)

channel_filter.update_team = True

alert_group = Alert.create(
title="the title",
message="the message",
alert_receive_channel=alert_receive_channel,
raw_request_data={},
integration_unique_data={},
image_url=None,
link_to_upstream_details=None,
channel_filter=channel_filter
).group

assert list(alert_group.teams.all()) == [team]

@pytest.mark.django_db
def test_channel_filter_no_team_set(
make_organization_and_user_with_plugin_token,
make_team,
make_escalation_chain,
make_alert_receive_channel,
make_channel_filter,
):
organization, user, token = make_organization_and_user_with_plugin_token()
team = make_team(organization)

alert_receive_channel = make_alert_receive_channel(organization, team=team)

escalation_chain = make_escalation_chain(organization=organization)

channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain, is_default=True)

alert_group = Alert.create(
title="the title",
message="the message",
alert_receive_channel=alert_receive_channel,
raw_request_data={},
integration_unique_data={},
image_url=None,
link_to_upstream_details=None
).group

assert list(alert_group.teams.all()) == [team]

@pytest.mark.django_db
def test_channel_filter_no_escalation_chain(
make_organization_and_user_with_plugin_token,
make_team,
make_escalation_chain,
make_alert_receive_channel,
make_channel_filter,
):
organization, user, token = make_organization_and_user_with_plugin_token()
team = make_team(organization)

alert_receive_channel = make_alert_receive_channel(organization)

escalation_chain = make_escalation_chain(organization=organization)
escalation_chain.team = team

make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain, is_default=True)
channel_filter = make_channel_filter(alert_receive_channel, is_default=True)

channel_filter.update_team = True

alert_group = Alert.create(
title="the title",
message="the message",
alert_receive_channel=alert_receive_channel,
raw_request_data={},
integration_unique_data={},
image_url=None,
link_to_upstream_details=None,
channel_filter=channel_filter
).group

assert list(alert_group.teams.all()) == []
22 changes: 19 additions & 3 deletions engine/apps/api/serializers/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .alert_receive_channel import FastAlertReceiveChannelSerializer
from .alerts_field_cache_buster_mixin import AlertsFieldCacheBusterMixin
from .user import FastUserSerializer, UserShortSerializer
from .team import FastTeamSerializer

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
Expand Down Expand Up @@ -125,8 +126,7 @@ class AlertGroupListSerializer(
related_users = serializers.SerializerMethodField()
dependent_alert_groups = ShortAlertGroupSerializer(many=True)
root_alert_group = ShortAlertGroupSerializer()
team = TeamPrimaryKeyRelatedField(source="channel.team", allow_null=True)

teams = serializers.SerializerMethodField()
alerts_count = serializers.IntegerField(read_only=True)
render_for_web = serializers.SerializerMethodField()

Expand All @@ -136,6 +136,7 @@ class AlertGroupListSerializer(
"dependent_alert_groups",
"log_records__author",
"labels",
"teams",
Prefetch(
"slack_messages",
queryset=SlackMessage.objects.select_related("_slack_team_identity").order_by("created_at")[:1],
Expand Down Expand Up @@ -187,12 +188,26 @@ class Meta:
"root_alert_group",
"status",
"declare_incident_link",
"team",
"grafana_incident_id",
"labels",
"permalinks",
"teams"
]

@extend_schema_field(FastTeamSerializer(many=True))
def get_teams(self, obj: "AlertGroup"):
"""
Handle AlertGroups that haven't been assigned a team yet
"""

if obj.teams.exists():
teams = obj.teams
elif obj.channel.team:
teams = [obj.channel.team]
else:
teams = []
return FastTeamSerializer(teams, context=self.context, many=True).data

def get_render_for_web(self, obj: "AlertGroup") -> RenderForWeb | EmptyRenderForWeb:
if not obj.last_alert:
return {}
Expand Down Expand Up @@ -231,6 +246,7 @@ def get_related_users(self, obj: "AlertGroup"):
return UserShortSerializer(users, context=self.context, many=True).data



class AlertGroupSerializer(AlertGroupListSerializer):
alerts = serializers.SerializerMethodField("get_limited_alerts")
last_alert_at = serializers.SerializerMethodField()
Expand Down
2 changes: 2 additions & 0 deletions engine/apps/api/serializers/channel_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Meta:
"notification_backends",
"filtering_term_as_jinja2",
"telegram_channel_details",
"update_team"
]
read_only_fields = [
"created_at",
Expand Down Expand Up @@ -165,6 +166,7 @@ class Meta:
"notify_in_slack",
"notify_in_telegram",
"notification_backends",
"update_team"
]
read_only_fields = ["created_at", "is_default"]

Expand Down
80 changes: 72 additions & 8 deletions engine/apps/api/tests/test_alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from apps.alerts.constants import ActionSource
from apps.alerts.models import (
Alert,
AlertGroup,
AlertGroupExternalID,
AlertGroupLogRecord,
Expand Down Expand Up @@ -887,14 +888,35 @@ def test_get_filter_by_teams(
alert_receive_channel_1 = make_alert_receive_channel(organization, team=team1)
alert_receive_channel_2 = make_alert_receive_channel(organization, team=team2)

alert_group_0 = make_alert_group(alert_receive_channel_0)
make_alert(alert_group=alert_group_0, raw_request_data=alert_raw_request_data)

alert_group_1 = make_alert_group(alert_receive_channel_1)
make_alert(alert_group=alert_group_1, raw_request_data=alert_raw_request_data)

alert_group_2 = make_alert_group(alert_receive_channel_2)
make_alert(alert_group=alert_group_2, raw_request_data=alert_raw_request_data)
alert_group_0 = Alert.create(
Copy link
Author

Choose a reason for hiding this comment

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

I had to switch to Alert.create here so that the team gets assigned correctly and as expected for the test to work.

title="the title",
message="the message",
alert_receive_channel=alert_receive_channel_0,
raw_request_data={},
integration_unique_data={},
image_url=None,
link_to_upstream_details=None,
).group

alert_group_1 = Alert.create(
title="the title",
message="the message",
alert_receive_channel=alert_receive_channel_1,
raw_request_data={},
integration_unique_data={},
image_url=None,
link_to_upstream_details=None,
).group

alert_group_2 = Alert.create(
title="the title",
message="the message",
alert_receive_channel=alert_receive_channel_2,
raw_request_data={},
integration_unique_data={},
image_url=None,
link_to_upstream_details=None,
).group

url = reverse("api-internal:alertgroup-list")

Expand Down Expand Up @@ -2413,3 +2435,45 @@ def test_filter_default_started_at(
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["pk"] == old_alert_group.public_primary_key



@pytest.mark.django_db
def test_update_team(
make_organization_and_user_with_plugin_token,
make_alert_receive_channel,
make_alert_group,
make_alert,
make_user_auth_headers,
make_escalation_chain,
make_team,
make_channel_filter
):
organization, user, token = make_organization_and_user_with_plugin_token()

team = make_team(organization)

alert_receive_channel = make_alert_receive_channel(organization)

escalation_chain = make_escalation_chain(organization=organization, team=team)

channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain, is_default=True, update_team=True)


alert_group = Alert.create(
title="the title",
message="the message",
alert_receive_channel=alert_receive_channel,
raw_request_data={},
integration_unique_data={},
image_url=None,
link_to_upstream_details=None
).group

client = APIClient()
url = reverse("api-internal:alertgroup-detail", kwargs={"pk": alert_group.public_primary_key})

response = client.get(url, **make_user_auth_headers(user, token))

assert response.status_code == status.HTTP_200_OK
assert response.json()['teams'][0]["id"] == team.public_primary_key
Loading