Coverage for CIResults/serializers.py: 89%
444 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 07:21 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 07:21 +0000
1import datetime
2from django.db import transaction
3from collections import namedtuple, OrderedDict
5from django.db.models import QuerySet
6from rest_framework import serializers
8from .run_import import RunConfigResults, ResultsCommitHandler, TestsuiteResults, TestsuiteTestResult
9from .models import Bug, Component, Build, Test, Machine, RunConfigTag, RunConfig, ReplicationScript, UnknownFailure
10from .models import TestSuite, TextStatus, IssueFilter, MachineTag, BugTrackerAccount
11from .models import Issue, BugTracker
13from shortener.models import Shortener
14from drf_spectacular.utils import extend_schema_field
15from drf_spectacular.types import OpenApiTypes
18@extend_schema_field(OpenApiTypes.NUMBER)
19class TimedeltaField(serializers.Field):
20 def to_internal_value(self, data):
21 try:
22 if isinstance(data, datetime.timedelta):
23 return data
24 if isinstance(data, int) or isinstance(data, float):
25 return datetime.timedelta(seconds=data)
26 if data is None:
27 return datetime.timedelta()
28 except (ValueError, TypeError):
29 pass
30 raise serializers.ValidationError("A valid number or timedelta is required.")
32 def to_representation(self, value):
33 return value.total_seconds()
36class UpdateMixin(serializers.ModelSerializer):
37 def get_extra_kwargs(self):
38 kwargs = super().get_extra_kwargs()
39 no_update_fields = getattr(self.Meta, "no_update_fields", None)
41 if self.instance and no_update_fields:
42 for field in no_update_fields:
43 kwargs.setdefault(field, {})
44 kwargs[field]["read_only"] = True
46 return kwargs
49class DynamicFieldsModelSerializer(serializers.ModelSerializer):
50 """
51 A ModelSerializer that takes an additional `extra_fields` argument that
52 controls which fields should be displayed. Subclass should define a
53 default_fields list.
54 """
55 default_fields = []
57 def __init__(self, *args, **kwargs):
58 extra_fields_to_serialize = kwargs.pop('extra_fields', None)
59 super().__init__(*args, **kwargs)
61 if extra_fields_to_serialize is not None:
62 # Drop extra fields that are not specified in the `extra_fields`
63 # argument.
64 default = set(self.default_fields)
65 existing = set(self.fields)
66 for field_name in existing - default - set(extra_fields_to_serialize):
67 self.fields.pop(field_name)
69 @classmethod
70 def extra_fields(cls) -> list[str]:
71 return list(set(cls.Meta.fields) - set(cls.default_fields))
74class RunConfigTagSerializer(serializers.ModelSerializer):
75 class Meta:
76 model = RunConfigTag
77 fields = ('id', 'description', 'url', 'public', '__str__')
78 read_only_fields = ('public', )
81class TestSuiteSerializer(serializers.ModelSerializer):
82 class Meta:
83 model = TestSuite
84 fields = ('id', '__str__')
87class TestSerializer(serializers.ModelSerializer):
88 testsuite = TestSuiteSerializer()
90 class Meta:
91 model = Test
92 fields = ('id', 'name', 'testsuite', 'public', 'vetted_on', 'first_runconfig', '__str__')
93 read_only_fields = ('public', )
96class MachineTagSerializer(serializers.ModelSerializer):
97 class Meta:
98 model = MachineTag
99 fields = ('id', 'name', 'public')
100 read_only_fields = ('added_on', )
103class MachineSerializer(serializers.ModelSerializer):
104 class Meta:
105 model = Machine
106 fields = ('id', 'public', 'vetted_on', '__str__')
107 read_only_fields = ('public', )
110class ImportMachineSerializer(serializers.ModelSerializer):
111 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=MachineTag.objects.all(), default=[])
112 aliases = serializers.SlugRelatedField(
113 slug_field='name', queryset=Machine.objects.all(), required=False, allow_null=True,
114 )
115 vetted = serializers.BooleanField(source="is_vetted_on", required=False, write_only=True)
117 class Meta:
118 model = Machine
119 fields = ('id', 'name', 'description', 'public', 'aliases', 'tags', "vetted", "vetted_on")
120 read_only_fields = ("vetted_on",)
121 extra_kwargs = {
122 "public": {"default": False},
123 }
125 def to_internal_value(self, data):
126 # HACK: Remove "vetted" field from the data, because the model has a read-only property `vetted`,
127 # which fails the serializer's validation
128 if data.get('vetted') is not None:
129 self.context["vetted"] = data.pop("vetted")
130 else:
131 self.context["vetted"] = False
132 if data.get("tags"):
133 machine_tags_db = set(MachineTag.objects.all().values_list("name", flat=True))
134 machine_tags = set(data["tags"])
135 # Get machine tags that doesn't exist in the database and insert them into context dictionary
136 new_tags_names = machine_tags - machine_tags_db
137 self.context["new_tags"] = [MachineTag(name=new_tag, public=True) for new_tag in new_tags_names]
138 # Leave only names of machine tags that exist in the database so that they pass the serializer's validation
139 data["tags"] = machine_tags & machine_tags_db
140 return super().to_internal_value(data)
142 @transaction.atomic
143 def create(self, validated_data):
144 machine = super().create(validated_data)
145 if new_tags := self.context.get("new_tags"):
146 new_tags = MachineTag.objects.bulk_create(self.context["new_tags"])
147 machine.tags.add(*new_tags)
148 if self.context.get("vetted"):
149 machine.vet()
150 machine.save()
151 return machine
154class TextStatusSerializer(serializers.ModelSerializer):
155 testsuite = TestSuiteSerializer()
157 class Meta:
158 model = TextStatus
159 fields = ('id', 'name', 'testsuite', '__str__')
162class IssueFilterSerializer(serializers.ModelSerializer):
163 tags = RunConfigTagSerializer(many=True)
164 tests = TestSerializer(many=True)
165 machine_tags = MachineTagSerializer(many=True)
166 machines = MachineSerializer(many=True)
167 statuses = TextStatusSerializer(many=True)
169 class Meta:
170 model = IssueFilter
171 fields = ('id', 'description', 'tags', 'machines', 'machine_tags', 'tests', 'statuses',
172 'stdout_regex', 'stderr_regex', 'dmesg_regex', 'user_query', '__str__')
173 read_only_fields = ('added_on', )
176class RunConfigSerializer(serializers.ModelSerializer):
177 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=RunConfigTag.objects.all())
178 builds = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all())
180 class Meta:
181 model = RunConfig
182 fields = ('id', 'name', 'tags', 'url', 'added_on', 'builds', 'environment', 'temporary', '__str__')
183 read_only_fields = ('added_on', )
185 def validate(self, data):
186 components = {}
187 for build in data["builds"]:
188 if build.component not in components:
189 components[build.component] = build
190 else:
191 raise serializers.ValidationError(
192 f"Two builds ({components[build.component]} and {build}) cannot be from the same component"
193 )
194 return data
197class ImportTestResultSerializer(serializers.Serializer):
198 id = serializers.IntegerField(read_only=True)
199 status = serializers.CharField()
200 duration = TimedeltaField(required=False, default=datetime.timedelta())
201 command = serializers.CharField(required=False, default="")
202 stdout = serializers.CharField(required=False, allow_null=True, write_only=True)
203 stderr = serializers.CharField(required=False, allow_null=True, write_only=True)
204 dmesg = serializers.CharField(required=False, allow_null=True, write_only=True)
205 url = serializers.URLField(required=False, allow_null=True)
206 start = serializers.DateTimeField(required=False, default=datetime.datetime.now())
207 test_name = serializers.CharField()
209 def to_representation(self, instance):
210 self.fields.pop("test_name", None)
211 representation = super().to_representation(instance)
212 representation["test_name"] = instance.test.name
213 return representation
216class ImportTestSuiteRunSerializer(serializers.Serializer):
217 runconfig_name = serializers.CharField(required=True, write_only=True)
218 runconfig = serializers.SerializerMethodField(read_only=True)
219 test_results = serializers.DictField(
220 child=serializers.DictField(
221 child=serializers.ListField(
222 child=ImportTestResultSerializer(),
223 required=True,
224 ),
225 required=True,
226 ),
227 required=True,
228 )
229 test_suite_name = serializers.CharField(required=True, write_only=True)
230 test_suite = serializers.SerializerMethodField(read_only=True)
232 @extend_schema_field({
233 "type": "object",
234 "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}
235 })
236 def get_runconfig(self, obj):
237 runconfig = obj["runconfig"]
238 return {"id": runconfig.id, "name": runconfig.name}
240 @extend_schema_field({
241 "type": "object",
242 "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}
243 })
244 def get_test_suite(self, obj):
245 test_suite = obj["test_suite"]
246 return {"id": test_suite.id, "name": test_suite.name}
248 @transaction.atomic
249 def create(self, validated_data):
250 run_results = []
251 try:
252 test_suite = Build.objects.get(name=validated_data["test_suite_name"])
253 test_suite_results = TestsuiteResults(
254 runconfig=None,
255 name=test_suite.component.name,
256 build=test_suite,
257 result_url_pattern=validated_data.get("result_url_pattern", ""),
258 format="json",
259 format_version=None,
260 )
261 except Build.DoesNotExist:
262 raise ValueError(f"Testsuite build {validated_data['test_suite_name']} does not exist")
263 for machine_name, raw_machine in validated_data.get("test_results").items():
264 for run_id, raw_run in raw_machine.items():
265 testsuite_test_results = []
266 for result in raw_run:
267 testsuite_test_results.append(TestsuiteTestResult(
268 name=result["test_name"],
269 status=result["status"],
270 start_time=result["start"],
271 duration=result.get("duration"),
272 command=result.get("command"),
273 stdout=result.get("stdout"),
274 stderr=result.get("stderr"),
275 dmesg=result.get("dmesg"),
276 url=result.get("url")
277 ))
278 run_results.append(test_suite_results.read_results(machine_name, int(run_id), testsuite_test_results))
280 rc = RunConfigResults(name=validated_data["runconfig_name"], run_results=run_results)
281 commit_handler = ResultsCommitHandler(rc)
282 commit_handler.commit()
283 return {
284 "runconfig": commit_handler.runconfig,
285 "test_results": commit_handler.test_results_by_machine_and_run,
286 "test_suite": test_suite,
287 }
290class MinimalMachineSerializer(serializers.ModelSerializer):
291 tags = serializers.StringRelatedField(many=True)
293 class Meta:
294 model = Machine
295 fields = ['name', 'tags']
298class MinimalRunConfigSerializer(serializers.ModelSerializer):
299 tags = serializers.StringRelatedField(many=True)
301 class Meta:
302 model = RunConfig
303 fields = ['name', 'tags']
306class UnknownFailureSerializer(DynamicFieldsModelSerializer):
307 test = serializers.StringRelatedField(source="result.test.name")
308 status = serializers.StringRelatedField(source="result.status.name")
309 dmesg = serializers.StringRelatedField(source="result.dmesg")
310 stdout = serializers.StringRelatedField(source="result.stdout")
311 stderr = serializers.StringRelatedField(source="result.stderr")
312 runconfig = MinimalRunConfigSerializer(source="result.ts_run.runconfig")
313 machine = MinimalMachineSerializer(source="result.ts_run.machine")
314 testsuite = serializers.StringRelatedField(source="result.ts_run.testsuite.name")
315 default_fields = ["id", "test", "status", "runconfig", "machine", "testsuite"]
317 class Meta:
318 model = UnknownFailure
319 fields = ["id", "test", "status", "dmesg", "stdout", "stderr", "runconfig", "machine", "testsuite"]
322class ComponentSerializer(serializers.ModelSerializer):
323 class Meta:
324 model = Component
325 fields = ('id', 'name', 'description', 'url', 'public', '__str__')
328class BuildSerializer(UpdateMixin, serializers.ModelSerializer):
329 component = serializers.SlugRelatedField(slug_field='name', queryset=Component.objects.all())
330 parents = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all())
332 class Meta:
333 model = Build
334 fields = ('id', 'name', 'component', 'version', 'added_on', 'parents',
335 'repo_type', 'branch', 'repo', 'upstream_url', 'parameters',
336 'build_log', '__str__')
337 read_only_fields = ('id', 'added_on')
338 no_update_fields = ['name', 'component']
341class BuildMinimalSerializer(serializers.ModelSerializer):
342 class Meta:
343 model = Build
344 fields = ('id', 'name', 'added_on', 'parents', 'upstream_url', '__str__')
345 read_only_fields = ('id', 'added_on')
348class RunConfigResultsSerializer(serializers.Serializer):
349 __str__ = serializers.CharField(max_length=255, read_only=True)
350 is_failure = serializers.BooleanField(read_only=True)
351 all_failures_covered = serializers.BooleanField(read_only=True)
352 bugs_covering = serializers.SerializerMethodField()
354 def get_bugs_covering(self, obj):
355 ser = serializers.ListField(child=serializers.CharField(max_length=255, read_only=True))
356 return ser.to_representation([b.short_name for b in obj.bugs_covering])
359class RunConfigResultsDiffSerializer(serializers.Serializer):
360 testsuite = serializers.SerializerMethodField()
361 test = serializers.SerializerMethodField()
362 machine = serializers.SerializerMethodField()
363 result_from = RunConfigResultsSerializer(read_only=True)
364 result_to = RunConfigResultsSerializer(read_only=True)
366 def get_testsuite(self, obj):
367 ser = serializers.CharField(max_length=255, read_only=True)
368 return ser.to_representation(obj.testsuite.name)
370 def get_test(self, obj):
371 ser = serializers.CharField(max_length=255, read_only=True)
372 return ser.to_representation(obj.test.name)
374 def get_machine(self, obj):
375 ser = serializers.CharField(max_length=255, read_only=True)
376 return ser.to_representation(obj.machine.name)
379class BugTrackerSerializer(serializers.ModelSerializer):
380 class Meta:
381 model = BugTracker
382 fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled',
383 'components_followed', 'components_followed_since', 'first_response_SLA')
384 read_only_fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled',
385 'components_followed', 'components_followed_since', 'first_response_SLA')
388def serialize_bug(bug, new_comments=None):
389 def _date_formatter(date_field):
390 return str(date_field) if date_field is not None else None
392 resp = {
393 'url': bug.url,
394 'bug_id': bug.bug_id,
395 'title': bug.title,
396 'description': bug.description,
397 'tracker': str(bug.tracker),
398 'created': _date_formatter(bug.created),
399 'updated': _date_formatter(bug.updated),
400 'polled': _date_formatter(bug.polled),
401 'closed': _date_formatter(bug.closed),
402 'creator': str(bug.creator),
403 'assignee': str(bug.assignee),
404 'product': bug.product,
405 'component': bug.component,
406 'priority': bug.priority,
407 'severity': bug.severity,
408 'features': bug.features_list,
409 'platforms': bug.platforms_list,
410 'status': bug.status,
411 'tags': bug.tags_list,
412 'custom_fields': bug.custom_fields,
413 'new_comments': []
414 }
415 if new_comments:
416 for comm in new_comments:
417 person = comm.db_object.account.person
418 author = person.full_name if person.full_name else person.email
419 resp['new_comments'].append({'author': author,
420 'created': str(comm.db_object.created_on),
421 'body': comm.body})
422 return resp
425class BugCompleteSerializer(serializers.ModelSerializer):
426 tracker = BugTrackerSerializer()
428 class Meta:
429 model = Bug
430 fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
431 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
432 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
433 'flagged_as_update_pending_on', 'custom_fields')
434 read_only_fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
435 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
436 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
437 'flagged_as_update_pending_on', 'custom_fields')
440class BugMinimalSerializer(serializers.ModelSerializer):
441 class Meta:
442 model = Bug
443 fields = ('id', 'short_name', 'url')
444 read_only_fields = ('id', 'short_name', 'url')
447class TrackerFromContextDefault:
448 requires_context = True
450 def __call__(self, serializer_field):
451 return serializer_field.context['view'].bugtracker
454class BugDetailSerializer(UpdateMixin, serializers.ModelSerializer):
455 tracker = BugTrackerSerializer(read_only=True, default=TrackerFromContextDefault())
456 assignee = serializers.SlugRelatedField(
457 slug_field='user_id', queryset=BugTrackerAccount.objects.all(), allow_null=True, required=False
458 )
459 creator = serializers.SlugRelatedField(
460 slug_field='user_id', queryset=BugTrackerAccount.objects.all(), allow_null=True, required=False
461 )
463 class Meta:
464 model = Bug
465 fields = ('id', 'bug_id', 'tracker', 'title', 'description', 'created',
466 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
467 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'parent',
468 'children', 'comments_polled')
469 read_only_fields = ('id', 'created', 'updated', 'polled', 'closed', 'children')
470 no_update_fields = ('bug_id', 'creator', 'parent')
473class ExecutionTimeSerializer(serializers.Serializer):
474 minimum = serializers.DurationField(read_only=True)
475 maximum = serializers.DurationField(read_only=True)
476 count = serializers.IntegerField(read_only=True)
479class RunConfigDiffSerializer(serializers.Serializer):
480 runcfg_from = RunConfigSerializer(read_only=True)
481 runcfg_to = RunConfigSerializer(read_only=True)
482 results = RunConfigResultsDiffSerializer(read_only=True, many=True)
483 new_tests = serializers.SerializerMethodField()
484 builds = serializers.SerializerMethodField()
485 bugs = BugMinimalSerializer(many=True)
486 status = serializers.CharField(max_length=10, read_only=True)
487 text = serializers.CharField(max_length=16000, read_only=True)
489 class BuildDiff2Serializer(serializers.Serializer):
490 component = ComponentSerializer(read_only=True)
491 from_build = BuildMinimalSerializer(read_only=True)
492 to_build = BuildMinimalSerializer(read_only=True)
494 class RunConfigDiffNewTestsSerializer(serializers.Serializer):
495 test = TestSerializer()
496 statuses = serializers.DictField(child=serializers.IntegerField(read_only=True))
497 exec_time = ExecutionTimeSerializer(read_only=True)
499 @extend_schema_field(serializers.DictField(child=BuildDiff2Serializer()))
500 def get_builds(self, obj):
501 bd2 = namedtuple('BuildDiff2', ['component', 'from_build', 'to_build'])
502 build_diffs = {k: bd2(k, v.from_build, v.to_build) for k, v in obj.builds.items()}
503 dict_ser = serializers.DictField(child=self.BuildDiff2Serializer())
504 return dict_ser.to_representation(build_diffs)
506 def __statuses(self, statuses):
507 return {k.name: v for k, v in statuses.items()}
509 @extend_schema_field(serializers.DictField(child=RunConfigDiffNewTestsSerializer()))
510 def get_new_tests(self, obj):
511 NT = namedtuple('NewTest', ['test', 'statuses', 'exec_time'])
512 new_tests = {k: NT(k, {k.name: v for k, v in v.to_statuses.items()}, v.to_exec_times)
513 for k, v in obj.new_tests.tests.items()}
514 dict_ser = serializers.DictField(child=self.RunConfigDiffNewTestsSerializer())
515 return dict_ser.to_representation(new_tests)
518class ReplicationScriptSerializer(serializers.ModelSerializer):
519 created_by = serializers.StringRelatedField()
520 source_tracker = serializers.StringRelatedField()
521 destination_tracker = serializers.StringRelatedField()
523 class Meta:
524 model = ReplicationScript
525 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script')
528class KnownIssuesSerializer(serializers.Serializer):
529 id = serializers.IntegerField(read_only=True)
530 testsuite = serializers.SerializerMethodField()
531 machine = serializers.SerializerMethodField()
532 run_id = serializers.SerializerMethodField()
533 test = serializers.SerializerMethodField()
534 status = serializers.SerializerMethodField()
535 url = serializers.SerializerMethodField()
537 bugs = serializers.SerializerMethodField()
539 def __init__(self, *args, **kwargs):
540 super().__init__(*args, **kwargs)
542 # Cache the serializers for performance reasons
543 self._char_ser = serializers.CharField(max_length=255, read_only=True)
544 self._int_ser = serializers.IntegerField(read_only=True)
545 self._bug_min_ser = BugMinimalSerializer(many=True)
547 @extend_schema_field(OpenApiTypes.INT)
548 def get_run_id(self, obj):
549 return self._int_ser.to_representation(obj.result.ts_run.run_id)
551 @extend_schema_field(OpenApiTypes.STR)
552 def get_testsuite(self, obj):
553 return self._char_ser.to_representation(obj.result.test.testsuite.name)
555 @extend_schema_field(OpenApiTypes.STR)
556 def get_test(self, obj):
557 return self._char_ser.to_representation(obj.result.test.name)
559 @extend_schema_field(OpenApiTypes.STR)
560 def get_machine(self, obj):
561 return self._char_ser.to_representation(obj.result.ts_run.machine.name)
563 @extend_schema_field(OpenApiTypes.STR)
564 def get_status(self, obj):
565 return self._char_ser.to_representation(obj.result.status.name)
567 @extend_schema_field(OpenApiTypes.STR)
568 def get_url(self, obj):
569 return self._char_ser.to_representation(obj.result.url)
571 @extend_schema_field(BugMinimalSerializer)
572 def get_bugs(self, obj):
573 return self._bug_min_ser.to_representation(obj.matched_ifa.issue.bugs.all())
576class BugTrackerAccountSerializer(serializers.ModelSerializer):
577 class Meta:
578 model = BugTrackerAccount
579 fields = ('id', 'is_developer')
580 read_only_fields = ('id', )
583class ShortenerSerializer(serializers.ModelSerializer):
584 class Meta:
585 model = Shortener
586 fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
587 read_only_fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
590class RateSerializer(serializers.Serializer):
591 count = serializers.IntegerField()
592 total = serializers.IntegerField()
593 percent = serializers.FloatField()
596class BugSerializer(serializers.ModelSerializer):
597 class Meta:
598 model = Bug
599 fields = ('short_name', 'title', 'url')
600 read_only_fields = ('short_name', 'title', 'url')
603class RestIssueSerializer(serializers.ModelSerializer):
604 class Meta:
605 model = Issue
606 fields = ('__all__')
607 read_only_fields = ('id', 'added_on', 'archived_on', 'runconfigs_covered_count', 'runconfigs_affected_count',
608 'last_seen', 'added_by', 'archived_by', 'last_seen_runconfig')
610 bugs = BugSerializer(many=True)
613class IssueSerializer(serializers.ModelSerializer):
614 class Meta:
615 model = Issue
616 fields = ('id', 'bugs', 'expected')
617 read_only_fields = ('id', 'bugs', 'expected')
619 bugs = BugSerializer(many=True)
622class IssueSerializerMinimal(serializers.ModelSerializer):
623 class Meta:
624 model = Issue
625 fields = ('id', )
626 read_only_fields = ('id', )
629def serialize_issue_hitrate(issues, minimal=False):
630 Serializer = IssueSerializerMinimal if minimal else IssueSerializer
632 ret = []
633 for issue, rate in issues.items():
634 val = Serializer(issue).data
635 val['hit_rate'] = RateSerializer(rate).data
636 ret.append(val)
637 return ret
640def serialize_MetricPassRatePerRunconfig(history):
641 runconfigs = OrderedDict()
642 for runconfig, _statuses in history.runconfigs.items():
643 runconfigs[str(runconfig)] = OrderedDict()
644 for status, rate in _statuses.items():
645 runconfigs[str(runconfig)][str(status)] = RateSerializer(rate).data
647 statuses = OrderedDict()
648 for status, _runconfigs in history.statuses.items():
649 statuses[str(status)] = OrderedDict()
650 for runconfig, rate in _runconfigs.items():
651 statuses[str(status)][str(runconfig)] = RateSerializer(rate).data
653 return {
654 "runconfigs": runconfigs,
655 "statuses": statuses,
656 "most_hit_issues": serialize_issue_hitrate(history.most_hit_issues),
657 "query_key": history.query.query_key,
658 }
661class IssueAddFilterSerializer(serializers.Serializer):
662 filters = serializers.ListField(
663 child=serializers.IntegerField(),
664 allow_empty=False,
665 write_only=True,
666 )
668 def to_internal_value(self, data: dict) -> dict:
669 internal: dict = super().to_internal_value(data)
670 filters: QuerySet[IssueFilter] = IssueFilter.objects.filter(id__in=internal["filters"])
671 found_ids: set[int] = set(filters.values_list("id", flat=True))
672 requested_ids: set[int] = set(internal["filters"])
673 missing_ids: set[int] = requested_ids - found_ids
675 if missing_ids:
676 raise serializers.ValidationError(
677 f"IssueFilter objects with IDs {sorted(missing_ids)} do not exist"
678 )
679 internal["filters"] = filters
680 return internal
683class PassRateStatisticsSerializer(serializers.Serializer):
684 passrate = RateSerializer()
685 runrate = RateSerializer()
686 discarded_rate = RateSerializer()
687 notrun_rate = RateSerializer()
690def serialize_MetricPassRatePerTest(metric_passrate):
691 discarded_status = "discarded (expected)"
693 tests = OrderedDict()
694 for test, results in metric_passrate.tests.items():
695 if results.is_fully_discarded:
696 tests[str(test)] = {
697 "status": discarded_status,
698 "is_pass": False,
699 "is_run": False,
700 "duration": str(results.duration),
701 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
702 }
703 else:
704 tests[str(test)] = {
705 "status": str(results.overall_result),
706 "is_pass": results.is_pass,
707 "is_run": not results.overall_result.is_notrun,
708 "duration": str(results.duration),
709 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
710 }
712 statuses = OrderedDict()
713 for status, rate in metric_passrate.statuses.items():
714 statuses[str(status)] = RateSerializer(rate).data
716 return {
717 "tests": tests,
718 "statuses": statuses,
719 "discarded_status": discarded_status,
720 "machines": [str(m) for m in metric_passrate.machines],
721 "runconfigs": RunConfigSerializer(metric_passrate.runconfigs, many=True).data,
722 "raw_statistics": PassRateStatisticsSerializer(metric_passrate.raw_statistics).data,
723 "statistics": PassRateStatisticsSerializer(metric_passrate.statistics).data,
724 "most_hit_issues": serialize_issue_hitrate(metric_passrate.most_hit_issues),
725 "uncovered_failure_rate": RateSerializer(metric_passrate.uncovered_failure_rate).data,
726 "notrun_rate": RateSerializer(metric_passrate.notrun_rate).data,
727 "most_interrupting_issues": serialize_issue_hitrate(metric_passrate.most_interrupting_issues),
728 "unknown_failure_interruption_rate": RateSerializer(metric_passrate.unknown_failure_interruption_rate).data,
729 "unexplained_interruption_rate": RateSerializer(metric_passrate.unexplained_interruption_rate).data,
730 "query_key": metric_passrate.query.query_key,
731 }