issues/serializers.py
Rama main · réplica de lectura del repositorio original.
import datetime
import json
from django.utils import timezone
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from bugsink.api_serializers import UTCModelSerializer
from bugsink.period_utils import DATEUTIL_KWARGS_MAP
from tags.models import IssueTag
from .models import Grouping, Issue, TurningPoint, TurningPointKind, issue_lookup_kwargs
class UnmuteVolumeConditionSerializer(serializers.Serializer):
period = serializers.CharField(help_text="Period unit, e.g. hour or day.")
nr_of_periods = serializers.IntegerField()
volume = serializers.IntegerField()
class IssueSerializer(UTCModelSerializer):
# grouping_keys = serializers.SerializerMethodField() # read-only list of strings
friendly_id = serializers.CharField(read_only=True)
fixed_at = serializers.ListField(
child=serializers.CharField(allow_blank=True), source="get_fixed_at", read_only=True,
help_text="Release versions at which this issue was marked fixed.")
events_at = serializers.ListField(
child=serializers.CharField(allow_blank=True), source="get_events_at", read_only=True,
help_text="Release versions in which events were seen. An empty string means no release was supplied.")
unmute_on_volume_based_conditions = UnmuteVolumeConditionSerializer(
source="get_unmute_on_volume_based_conditions", many=True, read_only=True,
help_text="Unmute when any condition is met: at least volume events within nr_of_periods of period.")
class Meta:
model = Issue
# TODO better wording:
# This is the first attempt at getting the list of fields right. My belief is: this is a nice minimal list.
# it _does_ contain `data`, which is typically quite "fat", but I'd say that's the most useful field to have.
# and when you're actually in the business of looking at a specific event, you want to see the data.
fields = [
"id",
"friendly_id",
"project",
"digest_order",
"last_seen",
"first_seen",
"digested_event_count",
"stored_event_count",
"calculated_type",
"calculated_value",
"transaction",
# "last_frame_filename",
# "last_frame_module",
# "last_frame_function",
"is_resolved",
"is_resolved_unconditionally",
"is_resolved_by_next_release",
"fixed_at",
"events_at",
"is_muted",
"unmute_after",
"unmute_on_volume_based_conditions",
# "grouping_keys", TODO (likely) once we have the "expand" idea implemented
]
# def get_grouping_keys(self, obj):
# # TODO: prefetch grouping_key in IssueViewSet
# return list(obj.grouping_set.values_list("grouping_key", flat=True))
class IssueTagSerializer(serializers.ModelSerializer):
key = serializers.CharField(source="key.key", read_only=True)
value = serializers.CharField(source="value.value", read_only=True)
class Meta:
model = IssueTag
fields = ["id", "key", "value", "count"]
read_only_fields = fields
class GroupingSerializer(serializers.ModelSerializer):
class Meta:
model = Grouping
fields = ["id", "grouping_key", "grouping_mechanism"]
read_only_fields = fields
class IssueMuteForSerializer(serializers.Serializer):
period_name = serializers.ChoiceField(choices=tuple(DATEUTIL_KWARGS_MAP.keys()))
nr_of_periods = serializers.IntegerField(min_value=1)
class IssueMuteUntilSerializer(IssueMuteForSerializer):
gte_threshold = serializers.IntegerField(min_value=1)
class IssueField(serializers.CharField):
def to_internal_value(self, value):
value = super().to_internal_value(value)
try:
return Issue.objects.filter(is_deleted=False).select_related("project").get(**issue_lookup_kwargs(value))
except Issue.DoesNotExist:
raise serializers.ValidationError("Issue not found.")
def to_representation(self, issue):
return str(issue.id) # JSON wants strings, not UUIDs.
class IssueCommentSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
issue = IssueField()
project = serializers.IntegerField(source="project_id", read_only=True)
timestamp = serializers.DateTimeField(read_only=True, default_timezone=datetime.timezone.utc)
comment = serializers.CharField(allow_blank=False, trim_whitespace=True)
user = serializers.IntegerField(source="user_id", read_only=True, allow_null=True)
def create(self, validated_data):
issue = validated_data["issue"]
return TurningPoint.objects.create(
project=issue.project,
issue=issue,
kind=TurningPointKind.MANUAL_ANNOTATION,
user=None, # Bearer-token API auth currently represents a global token, not a user.
comment=validated_data["comment"],
timestamp=timezone.now(),
)
class TurningPointSerializer(UTCModelSerializer):
user_display_name = serializers.SerializerMethodField(
help_text="Author's full name or username; null when no user is recorded.")
kind = serializers.SerializerMethodField()
metadata = serializers.SerializerMethodField(help_text=(
"Details of the state change, depending on `kind`:\n\n"
"- `resolved`: `resolved_unconditionally`, `resolve_by_next`, or `resolved_release`.\n"
"- `muted`: `mute_unconditionally`, `mute_for` (`period_name`, `nr_of_periods`, `unmute_after`), "
"or `mute_until` (`period_name`, `nr_of_periods`, `gte_threshold`).\n"
"- `unmuted`: `{}` for manual unmuting; `mute_for` (`unmute_after`) or "
"`mute_until` (`period`, `nr_of_periods`, `volume`) for automatic unmuting.\n"
"- `next_materialized`: `actual_release` names the release that fulfilled resolve-by-next.\n\n"
"Other kinds normally have `{}`. Release values are version strings; "
"`unmute_after` is a UTC timestamp. Thresholds may be strings or numbers."
))
class Meta:
model = TurningPoint
fields = [
"id",
"issue",
"project",
"triggering_event",
"user",
"user_display_name",
"timestamp",
"kind",
"comment",
"metadata",
]
read_only_fields = fields
@extend_schema_field(serializers.CharField(allow_null=True))
def get_user_display_name(self, obj):
if obj.user is None:
return None
return obj.user.get_full_name() or obj.user.username
@extend_schema_field(serializers.ChoiceField(choices=[kind.name.lower() for kind in TurningPointKind]))
def get_kind(self, obj):
return TurningPointKind(obj.kind).name.lower()
@extend_schema_field(serializers.JSONField)
def get_metadata(self, obj):
return json.loads(obj.metadata)