Ctrl K
ComunidadGuías…

issues/serializers.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)