Minor optimizations for public API endpoints (#5172)

- Add/update eager loading for public API serializers
- Use cached final schedule to get currently on-call users
This commit is contained in:
Matias Bordese 2024-10-11 16:39:39 -03:00 committed by GitHub
parent 673d2e9595
commit e0816dd1ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 40 additions and 13 deletions

View file

@ -2,14 +2,17 @@ from rest_framework import serializers
from apps.alerts.models import EscalationChain
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
from common.api_helpers.mixins import EagerLoadingMixin
from common.api_helpers.utils import CurrentOrganizationDefault
class EscalationChainSerializer(serializers.ModelSerializer):
class EscalationChainSerializer(EagerLoadingMixin, serializers.ModelSerializer):
id = serializers.ReadOnlyField(source="public_primary_key")
organization = serializers.HiddenField(default=CurrentOrganizationDefault())
team_id = TeamPrimaryKeyRelatedField(required=False, allow_null=True, source="team")
SELECT_RELATED = ["organization", "team"]
class Meta:
model = EscalationChain
fields = (

View file

@ -107,7 +107,13 @@ class EscalationPolicySerializer(EagerLoadingMixin, OrderedModelSerializer):
]
PREFETCH_RELATED = ["notify_to_users_queue"]
SELECT_RELATED = ["escalation_chain"]
SELECT_RELATED = [
"custom_webhook",
"escalation_chain",
"notify_schedule",
"notify_to_group",
"notify_to_team_members",
]
@cached_property
def escalation_chain(self):

View file

@ -85,7 +85,7 @@ class IntegrationSerializer(EagerLoadingMixin, serializers.ModelSerializer, Main
description_short = serializers.CharField(max_length=250, required=False, allow_null=True)
PREFETCH_RELATED = ["channel_filters"]
SELECT_RELATED = ["organization", "integration_heartbeat"]
SELECT_RELATED = ["organization", "integration_heartbeat", "team"]
class Meta:
model = AlertReceiveChannel

View file

@ -122,7 +122,7 @@ class CustomOnCallShiftSerializer(EagerLoadingMixin, serializers.ModelSerializer
"source": {"required": False, "write_only": True},
}
SELECT_RELATED = ["schedule"]
SELECT_RELATED = ["organization", "team", "schedule"]
PREFETCH_RELATED = ["schedules", "users"]
def create(self, validated_data):

View file

@ -4,6 +4,7 @@ from apps.alerts.models import AlertReceiveChannel, ChannelFilter, EscalationCha
from apps.base.messaging import get_messaging_backend_from_id, get_messaging_backends
from common.api_helpers.custom_fields import OrganizationFilteredPrimaryKeyRelatedField
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.mixins import EagerLoadingMixin
from common.api_helpers.utils import valid_jinja_template_for_serializer_method_field
from common.jinja_templater.apply_jinja_template import JinjaTemplateError
from common.ordered_model.serializer import OrderedModelSerializer
@ -129,7 +130,7 @@ class RoutingTypeField(fields.CharField):
raise BadRequest(detail="Invalid route type")
class ChannelFilterSerializer(BaseChannelFilterSerializer):
class ChannelFilterSerializer(EagerLoadingMixin, BaseChannelFilterSerializer):
id = serializers.CharField(read_only=True, source="public_primary_key")
slack = serializers.DictField(required=False)
telegram = serializers.DictField(required=False)
@ -146,6 +147,8 @@ class ChannelFilterSerializer(BaseChannelFilterSerializer):
is_the_last_route = serializers.BooleanField(read_only=True, source="is_default")
SELECT_RELATED = ["alert_receive_channel", "escalation_chain"]
class Meta:
model = ChannelFilter
fields = OrderedModelSerializer.Meta.fields + [

View file

@ -6,21 +6,26 @@ from apps.schedules.ical_utils import list_users_to_notify_from_ical
from apps.slack.models import SlackUserGroup
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.mixins import EagerLoadingMixin
class ScheduleBaseSerializer(serializers.ModelSerializer):
class ScheduleBaseSerializer(EagerLoadingMixin, serializers.ModelSerializer):
id = serializers.CharField(read_only=True, source="public_primary_key")
on_call_now = serializers.SerializerMethodField()
slack = serializers.DictField(required=False)
team_id = TeamPrimaryKeyRelatedField(required=False, allow_null=True, source="team")
SELECT_RELATED = ["team"]
def create(self, validated_data):
validated_data = self._correct_validated_data(validated_data)
validated_data["organization"] = self.context["request"].auth.organization
return super().create(validated_data)
def get_on_call_now(self, obj):
users_on_call = list_users_to_notify_from_ical(obj, datetime.datetime.now(datetime.timezone.utc))
users_on_call = list_users_to_notify_from_ical(
obj, datetime.datetime.now(datetime.timezone.utc), from_cached_final=True
)
if users_on_call is not None:
return [user.public_primary_key for user in users_on_call]
else:

View file

@ -5,11 +5,10 @@ from apps.schedules.tasks import (
schedule_notify_about_empty_shifts_in_schedule,
schedule_notify_about_gaps_in_schedule,
)
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField, TimeZoneField, UsersFilteredByOrganizationField
from common.api_helpers.custom_fields import TimeZoneField, UsersFilteredByOrganizationField
class ScheduleWebSerializer(ScheduleBaseSerializer):
team_id = TeamPrimaryKeyRelatedField(required=False, allow_null=True, source="team")
time_zone = TimeZoneField(required=True)
shifts = UsersFilteredByOrganizationField(
queryset=CustomOnCallShift.objects,

View file

@ -8,6 +8,7 @@ from apps.webhooks.models.webhook import PUBLIC_WEBHOOK_HTTP_METHODS, WEBHOOK_FI
from apps.webhooks.presets.preset_options import WebhookPresetOptions
from common.api_helpers.custom_fields import IntegrationFilteredByOrganizationField, TeamPrimaryKeyRelatedField
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.mixins import EagerLoadingMixin
from common.api_helpers.utils import CurrentOrganizationDefault, CurrentTeamDefault, CurrentUserDefault
from common.jinja_templater import apply_jinja_template
from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning
@ -48,7 +49,7 @@ class WebhookResponseSerializer(serializers.ModelSerializer):
]
class WebhookCreateSerializer(serializers.ModelSerializer):
class WebhookCreateSerializer(EagerLoadingMixin, serializers.ModelSerializer):
id = serializers.CharField(read_only=True, source="public_primary_key")
organization = serializers.HiddenField(default=CurrentOrganizationDefault())
team = TeamPrimaryKeyRelatedField(allow_null=True, default=CurrentTeamDefault())
@ -58,6 +59,8 @@ class WebhookCreateSerializer(serializers.ModelSerializer):
source="filtered_integrations", many=True, required=False
)
SELECT_RELATED = ["organization", "team"]
class Meta:
model = Webhook
fields = [

View file

@ -34,6 +34,7 @@ class EscalationChainView(RateLimitHeadersMixin, ModelViewSet):
if name is not None:
queryset = queryset.filter(name=name)
queryset = self.serializer_class.setup_eager_loading(queryset)
return queryset.order_by("id")
def get_object(self):

View file

@ -1,4 +1,3 @@
from django.db.models import Count
from django_filters import rest_framework as filters
from rest_framework.exceptions import NotFound
from rest_framework.permissions import IsAuthenticated
@ -47,7 +46,6 @@ class IntegrationView(
queryset = queryset.filter(verbal_name=name)
queryset = self.filter_queryset(queryset)
queryset = self.serializer_class.setup_eager_loading(queryset)
queryset = queryset.annotate(alert_groups_count_annotated=Count("alert_groups", distinct=True))
return queryset

View file

@ -37,6 +37,7 @@ class ChannelFilterView(RateLimitHeadersMixin, UpdateSerializerMixin, ModelViewS
queryset = ChannelFilter.objects.filter(
alert_receive_channel__organization=self.request.auth.organization, alert_receive_channel__deleted_at=None
)
queryset = self.serializer_class.setup_eager_loading(queryset)
if integration_id:
queryset = queryset.filter(alert_receive_channel__public_primary_key=integration_id)

View file

@ -57,6 +57,7 @@ class OnCallScheduleChannelView(RateLimitHeadersMixin, UpdateSerializerMixin, Mo
if name is not None:
queryset = queryset.filter(name=name)
queryset = self.serializer_class.setup_eager_loading(queryset)
return queryset.order_by("id")
def get_object(self):

View file

@ -42,6 +42,7 @@ class WebhooksView(RateLimitHeadersMixin, UpdateSerializerMixin, ModelViewSet):
if webhook_name:
queryset = queryset.filter(name=webhook_name)
queryset = self.serializer_class.setup_eager_loading(queryset)
return queryset.order_by("id")
def get_object(self):

View file

@ -344,6 +344,7 @@ def list_of_empty_shifts_in_schedule(
def list_users_to_notify_from_ical(
schedule: "OnCallSchedule",
events_datetime: typing.Optional[datetime.datetime] = None,
from_cached_final: bool = False,
) -> typing.Sequence["User"]:
"""
Retrieve on-call users for the current time
@ -353,6 +354,7 @@ def list_users_to_notify_from_ical(
schedule,
events_datetime,
events_datetime,
from_cached_final=from_cached_final,
)
@ -360,8 +362,12 @@ def list_users_to_notify_from_ical_for_period(
schedule: "OnCallSchedule",
start_datetime: datetime.datetime,
end_datetime: datetime.datetime,
from_cached_final: bool = False,
) -> typing.Sequence["User"]:
events = schedule.final_events(start_datetime, end_datetime)
if from_cached_final and schedule.cached_ical_final_schedule:
events = schedule.filter_events(start_datetime, end_datetime, from_cached_final=True)
else:
events = schedule.final_events(start_datetime, end_datetime)
usernames: typing.List[str] = []
for event in events:
usernames += [u["email"] for u in event.get("users", [])]