Rework alert group internal API team filter (#3413)

Related to https://github.com/grafana/oncall-private/issues/2177
This commit is contained in:
Matias Bordese 2023-11-23 14:28:00 -03:00 committed by GitHub
parent 60ef4348f5
commit 55fedb25d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 105 additions and 21 deletions

View file

@ -125,6 +125,7 @@ class AlertGroupListSerializer(
PREFETCH_RELATED = [
"dependent_alert_groups",
"log_records__author",
"labels",
]
SELECT_RELATED = [

View file

@ -863,6 +863,72 @@ def test_get_filter_escalation_chain(
assert len(response.data["results"]) == 2
@pytest.mark.django_db
def test_get_filter_by_teams(
make_organization_and_user_with_plugin_token,
make_team,
make_alert_receive_channel,
make_alert_group,
make_alert,
make_user_auth_headers,
):
client = APIClient()
organization, user, token = make_organization_and_user_with_plugin_token()
team1 = make_team(organization)
team2 = make_team(organization)
alert_receive_channel_0 = make_alert_receive_channel(organization)
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)
url = reverse("api-internal:alertgroup-list")
# check no team is given
response = client.get(url, **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
assert len(response.data["results"]) == 3
assert {ag["pk"] for ag in response.data["results"]} == {
alert_group_0.public_primary_key,
alert_group_1.public_primary_key,
alert_group_2.public_primary_key,
}
# check the "No team" case
response = client.get(url + "?team=null", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
assert len(response.data["results"]) == 1
assert {ag["pk"] for ag in response.data["results"]} == {alert_group_0.public_primary_key}
# check the "No team" + other team case
response = client.get(url + f"?team=null&team={team2.public_primary_key}", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
assert len(response.data["results"]) == 2
assert {ag["pk"] for ag in response.data["results"]} == {
alert_group_0.public_primary_key,
alert_group_2.public_primary_key,
}
# check the multiple teams case
response = client.get(
url + f"?team={team1.public_primary_key}&team={team2.public_primary_key}", **make_user_auth_headers(user, token)
)
assert response.status_code == status.HTTP_200_OK
assert len(response.data["results"]) == 2
assert {ag["pk"] for ag in response.data["results"]} == {
alert_group_1.public_primary_key,
alert_group_2.public_primary_key,
}
@pytest.mark.django_db
def test_get_filter_labels(
make_organization_and_user_with_plugin_token,

View file

@ -5,6 +5,8 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from common.api_helpers.filters import NO_TEAM_VALUE
@pytest.fixture()
def escalation_chain_internal_api_setup(make_organization_and_user_with_plugin_token, make_escalation_chain):
@ -103,7 +105,7 @@ def test_escalation_chain_copy(
escalation_chain = make_escalation_chain(organization, team=team)
data = {
"name": "escalation_chain_updated",
"team": new_team.public_primary_key if new_team else "null",
"team": new_team.public_primary_key if new_team else NO_TEAM_VALUE,
}
client = APIClient()
@ -125,6 +127,8 @@ def test_escalation_chain_copy_empty_name(
client = APIClient()
url = reverse("api-internal:escalation_chain-copy", kwargs={"pk": escalation_chain.public_primary_key})
response = client.post(url, {"name": "", "team": "null"}, format="json", **make_user_auth_headers(user, token))
response = client.post(
url, {"name": "", "team": NO_TEAM_VALUE}, format="json", **make_user_auth_headers(user, token)
)
assert response.status_code == status.HTTP_400_BAD_REQUEST

View file

@ -10,8 +10,9 @@ from apps.alerts.models import AlertReceiveChannel
from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.models import CustomOnCallShift, OnCallScheduleCalendar, OnCallScheduleWeb
from apps.user_management.models import Team
from common.api_helpers.filters import NO_TEAM_VALUE
GENERAL_TEAM = Team(public_primary_key="null", name="No team", email=None, avatar_url=None)
GENERAL_TEAM = Team(public_primary_key=NO_TEAM_VALUE, name="No team", email=None, avatar_url=None)
def get_payload_from_team(team, long=False):
@ -203,7 +204,7 @@ def test_teams_number_of_users_currently_oncall_attribute_works_properly(
team1.public_primary_key: 2,
team2.public_primary_key: 1,
team3.public_primary_key: 0,
"null": 0, # this covers the case of "No team"
NO_TEAM_VALUE: 0, # this covers the case of "No team"
}
for team in response.json():

View file

@ -28,12 +28,7 @@ from apps.labels.utils import is_labels_feature_enabled
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
from apps.user_management.models import Team, User
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.filters import (
ByTeamModelFieldFilterMixin,
DateRangeFilterMixin,
ModelFieldFilterMixin,
TeamModelMultipleChoiceFilter,
)
from common.api_helpers.filters import NO_TEAM_VALUE, DateRangeFilterMixin, ModelFieldFilterMixin
from common.api_helpers.mixins import PreviewTemplateMixin, PublicPrimaryKeyMixin, TeamFilteringMixin
from common.api_helpers.paginators import TwentyFiveCursorPaginator
@ -84,7 +79,7 @@ class AlertGroupFilterBackend(filters.DjangoFilterBackend):
return filterset
class AlertGroupFilter(DateRangeFilterMixin, ByTeamModelFieldFilterMixin, ModelFieldFilterMixin, filters.FilterSet):
class AlertGroupFilter(DateRangeFilterMixin, ModelFieldFilterMixin, filters.FilterSet):
"""
Examples of possible date formats here https://docs.djangoproject.com/en/1.9/ref/settings/#datetime-input-formats
"""
@ -141,7 +136,6 @@ class AlertGroupFilter(DateRangeFilterMixin, ByTeamModelFieldFilterMixin, ModelF
)
with_resolution_note = filters.BooleanFilter(method="filter_with_resolution_note")
mine = filters.BooleanFilter(method="filter_mine")
team = TeamModelMultipleChoiceFilter(field_name="channel__team")
class Meta:
model = AlertGroup
@ -337,6 +331,16 @@ class AlertGroupView(
if not ignore_filtering_by_available_teams:
alert_receive_channels_qs = alert_receive_channels_qs.filter(*self.available_teams_lookup_args)
# Filter by team(s). Since we really filter teams from integrations, this is not an AlertGroup model filter.
# This is based on the common.api_helpers.ByTeamModelFieldFilterMixin implementation
team_values = self.request.query_params.getlist("team", [])
if team_values:
null_team_lookup = Q(team__isnull=True) if NO_TEAM_VALUE in team_values else None
teams_lookup = Q(team__public_primary_key__in=[ppk for ppk in team_values if ppk != NO_TEAM_VALUE])
if null_team_lookup:
teams_lookup = teams_lookup | null_team_lookup
alert_receive_channels_qs = alert_receive_channels_qs.filter(teams_lookup)
alert_receive_channels_ids = list(alert_receive_channels_qs.values_list("id", flat=True))
queryset = AlertGroup.objects.filter(channel__in=alert_receive_channels_ids)

View file

@ -25,7 +25,7 @@ from apps.integrations.legacy_prefix import has_legacy_prefix, remove_legacy_pre
from apps.labels.utils import is_labels_feature_enabled
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.filters import ByTeamModelFieldFilterMixin, TeamModelMultipleChoiceFilter
from common.api_helpers.filters import NO_TEAM_VALUE, ByTeamModelFieldFilterMixin, TeamModelMultipleChoiceFilter
from common.api_helpers.mixins import (
FilterSerializerMixin,
PreviewTemplateException,
@ -231,7 +231,7 @@ class AlertReceiveChannelView(
raise BadRequest(detail="team_id must be specified")
team_id = request.query_params["team_id"]
if team_id == "null":
if team_id == NO_TEAM_VALUE:
team_id = None
try:

View file

@ -18,7 +18,12 @@ from apps.auth_token.auth import PluginAuthentication
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
from apps.user_management.models import Team
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.filters import ByTeamModelFieldFilterMixin, ModelFieldFilterMixin, TeamModelMultipleChoiceFilter
from common.api_helpers.filters import (
NO_TEAM_VALUE,
ByTeamModelFieldFilterMixin,
ModelFieldFilterMixin,
TeamModelMultipleChoiceFilter,
)
from common.api_helpers.mixins import (
FilterSerializerMixin,
ListSerializerMixin,
@ -128,7 +133,7 @@ class EscalationChainViewSet(
name = request.data.get("name")
team_id = request.data.get("team")
if team_id == "null":
if team_id == NO_TEAM_VALUE:
team_id = None
if not name:

View file

@ -10,6 +10,7 @@ from apps.auth_token.auth import PluginAuthentication
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
from apps.schedules.ical_utils import get_cached_oncall_users_for_multiple_schedules
from apps.user_management.models import Team
from common.api_helpers.filters import NO_TEAM_VALUE
from common.api_helpers.mixins import PublicPrimaryKeyMixin
@ -62,7 +63,7 @@ class TeamViewSet(
return TeamLongSerializer if self._is_long_request() else TeamSerializer
def list(self, request, *args, **kwargs):
general_team = [Team(public_primary_key="null", name="No team", email=None, avatar_url=None)]
general_team = [Team(public_primary_key=NO_TEAM_VALUE, name="No team", email=None, avatar_url=None)]
queryset = self.filter_queryset(self.get_queryset())
if self.request.query_params.get("only_include_notifiable_teams", "false") == "true":

View file

@ -16,7 +16,7 @@ from apps.public_api.helpers import is_valid_group_creation_date, team_has_slack
from apps.public_api.serializers import IncidentSerializer
from apps.public_api.throttlers.user_throttle import UserThrottle
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.filters import ByTeamModelFieldFilterMixin, get_team_queryset
from common.api_helpers.filters import NO_TEAM_VALUE, ByTeamModelFieldFilterMixin, get_team_queryset
from common.api_helpers.mixins import RateLimitHeadersMixin
from common.api_helpers.paginators import FiftyPageSizePaginator
@ -27,7 +27,7 @@ class IncidentByTeamFilter(ByTeamModelFieldFilterMixin, filters.FilterSet):
queryset=get_team_queryset,
to_field_name="public_primary_key",
null_label="noteam",
null_value="null",
null_value=NO_TEAM_VALUE,
method=ByTeamModelFieldFilterMixin.filter_model_field_with_single_value.__name__,
)

View file

@ -7,6 +7,8 @@ from django_filters.utils import handle_timezone
from apps.user_management.models import Team
from common.api_helpers.exceptions import BadRequest
NO_TEAM_VALUE = "null"
class DateRangeFilterMixin:
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S"
@ -100,7 +102,7 @@ class ByTeamFilter(ByTeamModelFieldFilterMixin, filters.FilterSet):
queryset=get_team_queryset,
to_field_name="public_primary_key",
null_label="noteam",
null_value="null",
null_value=NO_TEAM_VALUE,
method=ByTeamModelFieldFilterMixin.filter_model_field_with_single_value.__name__,
)
@ -112,7 +114,7 @@ class TeamModelMultipleChoiceFilter(filters.ModelMultipleChoiceFilter):
queryset=get_team_queryset,
to_field_name="public_primary_key",
null_label="noteam",
null_value="null",
null_value=NO_TEAM_VALUE,
method=ByTeamModelFieldFilterMixin.filter_model_field_with_multiple_values.__name__,
):
super().__init__(