Coverage for CIResults / serializers.py: 89%
431 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-04 08:33 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-04 08:33 +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 ExecutionTimeSerializer(serializers.Serializer):
448 minimum = serializers.DurationField(read_only=True)
449 maximum = serializers.DurationField(read_only=True)
450 count = serializers.IntegerField(read_only=True)
453class RunConfigDiffSerializer(serializers.Serializer):
454 runcfg_from = RunConfigSerializer(read_only=True)
455 runcfg_to = RunConfigSerializer(read_only=True)
456 results = RunConfigResultsDiffSerializer(read_only=True, many=True)
457 new_tests = serializers.SerializerMethodField()
458 builds = serializers.SerializerMethodField()
459 bugs = BugMinimalSerializer(many=True)
460 status = serializers.CharField(max_length=10, read_only=True)
461 text = serializers.CharField(max_length=16000, read_only=True)
463 class BuildDiff2Serializer(serializers.Serializer):
464 component = ComponentSerializer(read_only=True)
465 from_build = BuildMinimalSerializer(read_only=True)
466 to_build = BuildMinimalSerializer(read_only=True)
468 class RunConfigDiffNewTestsSerializer(serializers.Serializer):
469 test = TestSerializer()
470 statuses = serializers.DictField(child=serializers.IntegerField(read_only=True))
471 exec_time = ExecutionTimeSerializer(read_only=True)
473 @extend_schema_field(serializers.DictField(child=BuildDiff2Serializer()))
474 def get_builds(self, obj):
475 bd2 = namedtuple('BuildDiff2', ['component', 'from_build', 'to_build'])
476 build_diffs = {k: bd2(k, v.from_build, v.to_build) for k, v in obj.builds.items()}
477 dict_ser = serializers.DictField(child=self.BuildDiff2Serializer())
478 return dict_ser.to_representation(build_diffs)
480 def __statuses(self, statuses):
481 return {k.name: v for k, v in statuses.items()}
483 @extend_schema_field(serializers.DictField(child=RunConfigDiffNewTestsSerializer()))
484 def get_new_tests(self, obj):
485 NT = namedtuple('NewTest', ['test', 'statuses', 'exec_time'])
486 new_tests = {k: NT(k, {k.name: v for k, v in v.to_statuses.items()}, v.to_exec_times)
487 for k, v in obj.new_tests.tests.items()}
488 dict_ser = serializers.DictField(child=self.RunConfigDiffNewTestsSerializer())
489 return dict_ser.to_representation(new_tests)
492class ReplicationScriptSerializer(serializers.ModelSerializer):
493 created_by = serializers.StringRelatedField()
494 source_tracker = serializers.StringRelatedField()
495 destination_tracker = serializers.StringRelatedField()
497 class Meta:
498 model = ReplicationScript
499 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script')
502class KnownIssuesSerializer(serializers.Serializer):
503 id = serializers.IntegerField(read_only=True)
504 testsuite = serializers.SerializerMethodField()
505 machine = serializers.SerializerMethodField()
506 run_id = serializers.SerializerMethodField()
507 test = serializers.SerializerMethodField()
508 status = serializers.SerializerMethodField()
509 url = serializers.SerializerMethodField()
511 bugs = serializers.SerializerMethodField()
513 def __init__(self, *args, **kwargs):
514 super().__init__(*args, **kwargs)
516 # Cache the serializers for performance reasons
517 self._char_ser = serializers.CharField(max_length=255, read_only=True)
518 self._int_ser = serializers.IntegerField(read_only=True)
519 self._bug_min_ser = BugMinimalSerializer(many=True)
521 @extend_schema_field(OpenApiTypes.INT)
522 def get_run_id(self, obj):
523 return self._int_ser.to_representation(obj.result.ts_run.run_id)
525 @extend_schema_field(OpenApiTypes.STR)
526 def get_testsuite(self, obj):
527 return self._char_ser.to_representation(obj.result.test.testsuite.name)
529 @extend_schema_field(OpenApiTypes.STR)
530 def get_test(self, obj):
531 return self._char_ser.to_representation(obj.result.test.name)
533 @extend_schema_field(OpenApiTypes.STR)
534 def get_machine(self, obj):
535 return self._char_ser.to_representation(obj.result.ts_run.machine.name)
537 @extend_schema_field(OpenApiTypes.STR)
538 def get_status(self, obj):
539 return self._char_ser.to_representation(obj.result.status.name)
541 @extend_schema_field(OpenApiTypes.STR)
542 def get_url(self, obj):
543 return self._char_ser.to_representation(obj.result.url)
545 @extend_schema_field(BugMinimalSerializer)
546 def get_bugs(self, obj):
547 return self._bug_min_ser.to_representation(obj.matched_ifa.issue.bugs.all())
550class BugTrackerAccountSerializer(serializers.ModelSerializer):
551 class Meta:
552 model = BugTrackerAccount
553 fields = ('id', 'is_developer')
554 read_only_fields = ('id', )
557class ShortenerSerializer(serializers.ModelSerializer):
558 class Meta:
559 model = Shortener
560 fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
561 read_only_fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
564class RateSerializer(serializers.Serializer):
565 count = serializers.IntegerField()
566 total = serializers.IntegerField()
567 percent = serializers.FloatField()
570class BugSerializer(serializers.ModelSerializer):
571 class Meta:
572 model = Bug
573 fields = ('short_name', 'title', 'url')
574 read_only_fields = ('short_name', 'title', 'url')
577class RestIssueSerializer(serializers.ModelSerializer):
578 class Meta:
579 model = Issue
580 fields = ('__all__')
581 read_only_fields = ('id', 'added_on', 'archived_on', 'runconfigs_covered_count', 'runconfigs_affected_count',
582 'last_seen', 'added_by', 'archived_by', 'last_seen_runconfig')
584 bugs = BugSerializer(many=True)
587class IssueSerializer(serializers.ModelSerializer):
588 class Meta:
589 model = Issue
590 fields = ('id', 'bugs', 'expected')
591 read_only_fields = ('id', 'bugs', 'expected')
593 bugs = BugSerializer(many=True)
596class IssueSerializerMinimal(serializers.ModelSerializer):
597 class Meta:
598 model = Issue
599 fields = ('id', )
600 read_only_fields = ('id', )
603def serialize_issue_hitrate(issues, minimal=False):
604 Serializer = IssueSerializerMinimal if minimal else IssueSerializer
606 ret = []
607 for issue, rate in issues.items():
608 val = Serializer(issue).data
609 val['hit_rate'] = RateSerializer(rate).data
610 ret.append(val)
611 return ret
614def serialize_MetricPassRatePerRunconfig(history):
615 runconfigs = OrderedDict()
616 for runconfig, _statuses in history.runconfigs.items():
617 runconfigs[str(runconfig)] = OrderedDict()
618 for status, rate in _statuses.items():
619 runconfigs[str(runconfig)][str(status)] = RateSerializer(rate).data
621 statuses = OrderedDict()
622 for status, _runconfigs in history.statuses.items():
623 statuses[str(status)] = OrderedDict()
624 for runconfig, rate in _runconfigs.items():
625 statuses[str(status)][str(runconfig)] = RateSerializer(rate).data
627 return {
628 "runconfigs": runconfigs,
629 "statuses": statuses,
630 "most_hit_issues": serialize_issue_hitrate(history.most_hit_issues),
631 "query_key": history.query.query_key,
632 }
635class IssueAddFilterSerializer(serializers.Serializer):
636 filters = serializers.ListField(
637 child=serializers.IntegerField(),
638 allow_empty=False,
639 write_only=True,
640 )
642 def to_internal_value(self, data: dict) -> dict:
643 internal: dict = super().to_internal_value(data)
644 filters: QuerySet[IssueFilter] = IssueFilter.objects.filter(id__in=internal["filters"])
645 found_ids: set[int] = set(filters.values_list("id", flat=True))
646 requested_ids: set[int] = set(internal["filters"])
647 missing_ids: set[int] = requested_ids - found_ids
649 if missing_ids:
650 raise serializers.ValidationError(
651 f"IssueFilter objects with IDs {sorted(missing_ids)} do not exist"
652 )
653 internal["filters"] = filters
654 return internal
657class PassRateStatisticsSerializer(serializers.Serializer):
658 passrate = RateSerializer()
659 runrate = RateSerializer()
660 discarded_rate = RateSerializer()
661 notrun_rate = RateSerializer()
664def serialize_MetricPassRatePerTest(metric_passrate):
665 discarded_status = "discarded (expected)"
667 tests = OrderedDict()
668 for test, results in metric_passrate.tests.items():
669 if results.is_fully_discarded:
670 tests[str(test)] = {
671 "status": discarded_status,
672 "is_pass": False,
673 "is_run": False,
674 "duration": str(results.duration),
675 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
676 }
677 else:
678 tests[str(test)] = {
679 "status": str(results.overall_result),
680 "is_pass": results.is_pass,
681 "is_run": not results.overall_result.is_notrun,
682 "duration": str(results.duration),
683 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
684 }
686 statuses = OrderedDict()
687 for status, rate in metric_passrate.statuses.items():
688 statuses[str(status)] = RateSerializer(rate).data
690 return {
691 "tests": tests,
692 "statuses": statuses,
693 "discarded_status": discarded_status,
694 "machines": [str(m) for m in metric_passrate.machines],
695 "runconfigs": RunConfigSerializer(metric_passrate.runconfigs, many=True).data,
696 "raw_statistics": PassRateStatisticsSerializer(metric_passrate.raw_statistics).data,
697 "statistics": PassRateStatisticsSerializer(metric_passrate.statistics).data,
698 "most_hit_issues": serialize_issue_hitrate(metric_passrate.most_hit_issues),
699 "uncovered_failure_rate": RateSerializer(metric_passrate.uncovered_failure_rate).data,
700 "notrun_rate": RateSerializer(metric_passrate.notrun_rate).data,
701 "most_interrupting_issues": serialize_issue_hitrate(metric_passrate.most_interrupting_issues),
702 "unknown_failure_interruption_rate": RateSerializer(metric_passrate.unknown_failure_interruption_rate).data,
703 "unexplained_interruption_rate": RateSerializer(metric_passrate.unexplained_interruption_rate).data,
704 "query_key": metric_passrate.query.query_key,
705 }