Rework alert group internal API team filter (#3413)
Related to https://github.com/grafana/oncall-private/issues/2177
This commit is contained in:
parent
60ef4348f5
commit
55fedb25d6
10 changed files with 105 additions and 21 deletions
|
|
@ -125,6 +125,7 @@ class AlertGroupListSerializer(
|
|||
PREFETCH_RELATED = [
|
||||
"dependent_alert_groups",
|
||||
"log_records__author",
|
||||
"labels",
|
||||
]
|
||||
|
||||
SELECT_RELATED = [
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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__,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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__(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue