Coverage for CIResults/serializers.py: 89%
419 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-06 08:12 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-06 08:12 +0000
1import datetime
2from django.db import transaction
3from collections import namedtuple, OrderedDict
4from rest_framework import serializers
6from .run_import import RunConfigResults, ResultsCommitHandler, TestsuiteResults, TestsuiteTestResult
7from .models import Bug, Component, Build, Test, Machine, RunConfigTag, RunConfig, ReplicationScript, UnknownFailure
8from .models import TestSuite, TextStatus, IssueFilter, MachineTag, BugTrackerAccount
9from .models import Issue, BugTracker
11from shortener.models import Shortener
12from drf_spectacular.utils import extend_schema_field
13from drf_spectacular.types import OpenApiTypes
16@extend_schema_field(OpenApiTypes.NUMBER)
17class TimedeltaField(serializers.Field):
18 def to_internal_value(self, data):
19 try:
20 if isinstance(data, datetime.timedelta):
21 return data
22 if isinstance(data, int) or isinstance(data, float):
23 return datetime.timedelta(seconds=data)
24 if data is None:
25 return datetime.timedelta()
26 except (ValueError, TypeError):
27 pass
28 raise serializers.ValidationError("A valid number or timedelta is required.")
30 def to_representation(self, value):
31 return value.total_seconds()
34class UpdateMixin(serializers.ModelSerializer):
35 def get_extra_kwargs(self):
36 kwargs = super().get_extra_kwargs()
37 no_update_fields = getattr(self.Meta, "no_update_fields", None)
39 if self.instance and no_update_fields:
40 for field in no_update_fields:
41 kwargs.setdefault(field, {})
42 kwargs[field]["read_only"] = True
44 return kwargs
47class DynamicFieldsModelSerializer(serializers.ModelSerializer):
48 """
49 A ModelSerializer that takes an additional `extra_fields` argument that
50 controls which fields should be displayed. Subclass should define a
51 default_fields list.
52 """
53 default_fields = []
55 def __init__(self, *args, **kwargs):
56 extra_fields_to_serialize = kwargs.pop('extra_fields', None)
57 super().__init__(*args, **kwargs)
59 if extra_fields_to_serialize is not None:
60 # Drop extra fields that are not specified in the `extra_fields`
61 # argument.
62 default = set(self.default_fields)
63 existing = set(self.fields)
64 for field_name in existing - default - set(extra_fields_to_serialize):
65 self.fields.pop(field_name)
67 @classmethod
68 def extra_fields(cls) -> list[str]:
69 return list(set(cls.Meta.fields) - set(cls.default_fields))
72class RunConfigTagSerializer(serializers.ModelSerializer):
73 class Meta:
74 model = RunConfigTag
75 fields = ('id', 'description', 'url', 'public', '__str__')
76 read_only_fields = ('public', )
79class TestSuiteSerializer(serializers.ModelSerializer):
80 class Meta:
81 model = TestSuite
82 fields = ('id', '__str__')
85class TestSerializer(serializers.ModelSerializer):
86 testsuite = TestSuiteSerializer()
88 class Meta:
89 model = Test
90 fields = ('id', 'name', 'testsuite', 'public', 'vetted_on', 'first_runconfig', '__str__')
91 read_only_fields = ('public', )
94class MachineTagSerializer(serializers.ModelSerializer):
95 class Meta:
96 model = MachineTag
97 fields = ('id', 'name', 'public')
98 read_only_fields = ('added_on', )
101class MachineSerializer(serializers.ModelSerializer):
102 class Meta:
103 model = Machine
104 fields = ('id', 'public', 'vetted_on', '__str__')
105 read_only_fields = ('public', )
108class ImportMachineSerializer(serializers.ModelSerializer):
109 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=MachineTag.objects.all(), default=[])
110 aliases = serializers.SlugRelatedField(
111 slug_field='name', queryset=Machine.objects.all(), required=False, allow_null=True,
112 )
113 vetted = serializers.BooleanField(source="is_vetted_on", required=False, write_only=True)
115 class Meta:
116 model = Machine
117 fields = ('id', 'name', 'description', 'public', 'aliases', 'tags', "vetted", "vetted_on")
118 read_only_fields = ("vetted_on",)
119 extra_kwargs = {
120 "public": {"default": False},
121 }
123 def to_internal_value(self, data):
124 # HACK: Remove "vetted" field from the data, because the model has a read-only property `vetted`,
125 # which fails the serializer's validation
126 if data.get('vetted') is not None:
127 self.context["vetted"] = data.pop("vetted")
128 else:
129 self.context["vetted"] = False
130 if data.get("tags"):
131 machine_tags_db = set(MachineTag.objects.all().values_list("name", flat=True))
132 machine_tags = set(data["tags"])
133 # Get machine tags that doesn't exist in the database and insert them into context dictionary
134 new_tags_names = machine_tags - machine_tags_db
135 self.context["new_tags"] = [MachineTag(name=new_tag, public=True) for new_tag in new_tags_names]
136 # Leave only names of machine tags that exist in the database so that they pass the serializer's validation
137 data["tags"] = machine_tags & machine_tags_db
138 return super().to_internal_value(data)
140 @transaction.atomic
141 def create(self, validated_data):
142 machine = super().create(validated_data)
143 if new_tags := self.context.get("new_tags"):
144 new_tags = MachineTag.objects.bulk_create(self.context["new_tags"])
145 machine.tags.add(*new_tags)
146 if self.context.get("vetted"):
147 machine.vet()
148 machine.save()
149 return machine
152class TextStatusSerializer(serializers.ModelSerializer):
153 testsuite = TestSuiteSerializer()
155 class Meta:
156 model = TextStatus
157 fields = ('id', 'name', 'testsuite', '__str__')
160class IssueFilterSerializer(serializers.ModelSerializer):
161 tags = RunConfigTagSerializer(many=True)
162 tests = TestSerializer(many=True)
163 machine_tags = MachineTagSerializer(many=True)
164 machines = MachineSerializer(many=True)
165 statuses = TextStatusSerializer(many=True)
167 class Meta:
168 model = IssueFilter
169 fields = ('id', 'description', 'tags', 'machines', 'machine_tags', 'tests', 'statuses',
170 'stdout_regex', 'stderr_regex', 'dmesg_regex', 'user_query', '__str__')
171 read_only_fields = ('added_on', )
174class RunConfigSerializer(serializers.ModelSerializer):
175 tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=RunConfigTag.objects.all())
176 builds = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all())
178 class Meta:
179 model = RunConfig
180 fields = ('id', 'name', 'tags', 'url', 'added_on', 'builds', 'environment', 'temporary', '__str__')
181 read_only_fields = ('added_on', )
183 def validate(self, data):
184 components = {}
185 for build in data["builds"]:
186 if build.component not in components:
187 components[build.component] = build
188 else:
189 raise serializers.ValidationError(
190 f"Two builds ({components[build.component]} and {build}) cannot be from the same component"
191 )
192 return data
195class ImportTestResultSerializer(serializers.Serializer):
196 id = serializers.IntegerField(read_only=True)
197 status = serializers.CharField()
198 duration = TimedeltaField(required=False, default=datetime.timedelta())
199 command = serializers.CharField(required=False, default="")
200 stdout = serializers.CharField(required=False, allow_null=True, write_only=True)
201 stderr = serializers.CharField(required=False, allow_null=True, write_only=True)
202 dmesg = serializers.CharField(required=False, allow_null=True, write_only=True)
203 url = serializers.URLField(required=False, allow_null=True)
204 start = serializers.DateTimeField(required=False, default=datetime.datetime.now())
205 test_name = serializers.CharField()
207 def to_representation(self, instance):
208 self.fields.pop("test_name", None)
209 representation = super().to_representation(instance)
210 representation["test_name"] = instance.test.name
211 return representation
214class ImportTestSuiteRunSerializer(serializers.Serializer):
215 runconfig_name = serializers.CharField(required=True, write_only=True)
216 runconfig = serializers.SerializerMethodField(read_only=True)
217 test_results = serializers.DictField(
218 child=serializers.DictField(
219 child=serializers.ListField(
220 child=ImportTestResultSerializer(),
221 required=True,
222 ),
223 required=True,
224 ),
225 required=True,
226 )
227 test_suite_name = serializers.CharField(required=True, write_only=True)
228 test_suite = serializers.SerializerMethodField(read_only=True)
230 @extend_schema_field({
231 "type": "object",
232 "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}
233 })
234 def get_runconfig(self, obj):
235 runconfig = obj["runconfig"]
236 return {"id": runconfig.id, "name": runconfig.name}
238 @extend_schema_field({
239 "type": "object",
240 "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}
241 })
242 def get_test_suite(self, obj):
243 test_suite = obj["test_suite"]
244 return {"id": test_suite.id, "name": test_suite.name}
246 @transaction.atomic
247 def create(self, validated_data):
248 run_results = []
249 try:
250 test_suite = Build.objects.get(name=validated_data["test_suite_name"])
251 test_suite_results = TestsuiteResults(
252 runconfig=None,
253 name=test_suite.component.name,
254 build=test_suite,
255 result_url_pattern=validated_data.get("result_url_pattern", ""),
256 format="json",
257 format_version=None,
258 )
259 except Build.DoesNotExist:
260 raise ValueError(f"Testsuite build {validated_data['test_suite_name']} does not exist")
261 for machine_name, raw_machine in validated_data.get("test_results").items():
262 for run_id, raw_run in raw_machine.items():
263 testsuite_test_results = []
264 for result in raw_run:
265 testsuite_test_results.append(TestsuiteTestResult(
266 name=result["test_name"],
267 status=result["status"],
268 start_time=result["start"],
269 duration=result.get("duration"),
270 command=result.get("command"),
271 stdout=result.get("stdout"),
272 stderr=result.get("stderr"),
273 dmesg=result.get("dmesg"),
274 url=result.get("url")
275 ))
276 run_results.append(test_suite_results.read_results(machine_name, int(run_id), testsuite_test_results))
278 rc = RunConfigResults(name=validated_data["runconfig_name"], run_results=run_results)
279 commit_handler = ResultsCommitHandler(rc)
280 commit_handler.commit()
281 return {
282 "runconfig": commit_handler.runconfig,
283 "test_results": commit_handler.test_results_by_machine_and_run,
284 "test_suite": test_suite,
285 }
288class MinimalMachineSerializer(serializers.ModelSerializer):
289 tags = serializers.StringRelatedField(many=True)
291 class Meta:
292 model = Machine
293 fields = ['name', 'tags']
296class MinimalRunConfigSerializer(serializers.ModelSerializer):
297 tags = serializers.StringRelatedField(many=True)
299 class Meta:
300 model = RunConfig
301 fields = ['name', 'tags']
304class UnknownFailureSerializer(DynamicFieldsModelSerializer):
305 test = serializers.StringRelatedField(source="result.test.name")
306 status = serializers.StringRelatedField(source="result.status.name")
307 dmesg = serializers.StringRelatedField(source="result.dmesg")
308 stdout = serializers.StringRelatedField(source="result.stdout")
309 stderr = serializers.StringRelatedField(source="result.stderr")
310 runconfig = MinimalRunConfigSerializer(source="result.ts_run.runconfig")
311 machine = MinimalMachineSerializer(source="result.ts_run.machine")
312 testsuite = serializers.StringRelatedField(source="result.ts_run.testsuite.name")
313 default_fields = ["id", "test", "status", "runconfig", "machine", "testsuite"]
315 class Meta:
316 model = UnknownFailure
317 fields = ["id", "test", "status", "dmesg", "stdout", "stderr", "runconfig", "machine", "testsuite"]
320class ComponentSerializer(serializers.ModelSerializer):
321 class Meta:
322 model = Component
323 fields = ('id', 'name', 'description', 'url', 'public', '__str__')
326class BuildSerializer(UpdateMixin, serializers.ModelSerializer):
327 component = serializers.SlugRelatedField(slug_field='name', queryset=Component.objects.all())
328 parents = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Build.objects.all())
330 class Meta:
331 model = Build
332 fields = ('id', 'name', 'component', 'version', 'added_on', 'parents',
333 'repo_type', 'branch', 'repo', 'upstream_url', 'parameters',
334 'build_log', '__str__')
335 read_only_fields = ('id', 'added_on')
336 no_update_fields = ['name', 'component']
339class BuildMinimalSerializer(serializers.ModelSerializer):
340 class Meta:
341 model = Build
342 fields = ('id', 'name', 'added_on', 'parents', 'upstream_url', '__str__')
343 read_only_fields = ('id', 'added_on')
346class RunConfigResultsSerializer(serializers.Serializer):
347 __str__ = serializers.CharField(max_length=255, read_only=True)
348 is_failure = serializers.BooleanField(read_only=True)
349 all_failures_covered = serializers.BooleanField(read_only=True)
350 bugs_covering = serializers.SerializerMethodField()
352 def get_bugs_covering(self, obj):
353 ser = serializers.ListField(child=serializers.CharField(max_length=255, read_only=True))
354 return ser.to_representation([b.short_name for b in obj.bugs_covering])
357class RunConfigResultsDiffSerializer(serializers.Serializer):
358 testsuite = serializers.SerializerMethodField()
359 test = serializers.SerializerMethodField()
360 machine = serializers.SerializerMethodField()
361 result_from = RunConfigResultsSerializer(read_only=True)
362 result_to = RunConfigResultsSerializer(read_only=True)
364 def get_testsuite(self, obj):
365 ser = serializers.CharField(max_length=255, read_only=True)
366 return ser.to_representation(obj.testsuite.name)
368 def get_test(self, obj):
369 ser = serializers.CharField(max_length=255, read_only=True)
370 return ser.to_representation(obj.test.name)
372 def get_machine(self, obj):
373 ser = serializers.CharField(max_length=255, read_only=True)
374 return ser.to_representation(obj.machine.name)
377class BugTrackerSerializer(serializers.ModelSerializer):
378 class Meta:
379 model = BugTracker
380 fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled',
381 'components_followed', 'components_followed_since', 'first_response_SLA')
382 read_only_fields = ('id', 'name', 'short_name', 'project', 'separator', 'url', 'tracker_type', 'polled',
383 'components_followed', 'components_followed_since', 'first_response_SLA')
386def serialize_bug(bug, new_comments=None):
387 def _date_formatter(date_field):
388 return str(date_field) if date_field is not None else None
390 runconfig_list = list(RunConfig.objects
391 .filter(testsuiterun__testresult__known_failure__matched_ifa__issue__bugs=bug)
392 .values()
393 .order_by('added_on'))
394 resp = {
395 'url': bug.url,
396 'bug_id': bug.bug_id,
397 'title': bug.title,
398 'description': bug.description,
399 'tracker': str(bug.tracker),
400 'created': _date_formatter(bug.created),
401 'updated': _date_formatter(bug.updated),
402 'polled': _date_formatter(bug.polled),
403 'closed': _date_formatter(bug.closed),
404 'creator': str(bug.creator),
405 'assignee': str(bug.assignee),
406 'product': bug.product,
407 'component': bug.component,
408 'priority': bug.priority,
409 'severity': bug.severity,
410 'features': bug.features_list,
411 'platforms': bug.platforms_list,
412 'status': bug.status,
413 'tags': bug.tags_list,
414 'runconfigs': runconfig_list,
415 'custom_fields': bug.custom_fields,
416 'new_comments': []
417 }
418 if new_comments:
419 for comm in new_comments:
420 person = comm.db_object.account.person
421 author = person.full_name if person.full_name else person.email
422 resp['new_comments'].append({'author': author,
423 'created': str(comm.db_object.created_on),
424 'body': comm.body})
425 return resp
428class BugCompleteSerializer(serializers.ModelSerializer):
429 tracker = BugTrackerSerializer()
431 class Meta:
432 model = Bug
433 fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
434 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
435 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
436 'flagged_as_update_pending_on', 'custom_fields')
437 read_only_fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
438 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
439 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
440 'flagged_as_update_pending_on', 'custom_fields')
443class BugMinimalSerializer(serializers.ModelSerializer):
444 class Meta:
445 model = Bug
446 fields = ('id', 'short_name', 'url')
447 read_only_fields = ('id', 'short_name', 'url')
450class ExecutionTimeSerializer(serializers.Serializer):
451 minimum = serializers.DurationField(read_only=True)
452 maximum = serializers.DurationField(read_only=True)
453 count = serializers.IntegerField(read_only=True)
456class RunConfigDiffSerializer(serializers.Serializer):
457 runcfg_from = RunConfigSerializer(read_only=True)
458 runcfg_to = RunConfigSerializer(read_only=True)
459 results = RunConfigResultsDiffSerializer(read_only=True, many=True)
460 new_tests = serializers.SerializerMethodField()
461 builds = serializers.SerializerMethodField()
462 bugs = BugMinimalSerializer(many=True)
463 status = serializers.CharField(max_length=10, read_only=True)
464 text = serializers.CharField(max_length=16000, read_only=True)
466 class BuildDiff2Serializer(serializers.Serializer):
467 component = ComponentSerializer(read_only=True)
468 from_build = BuildMinimalSerializer(read_only=True)
469 to_build = BuildMinimalSerializer(read_only=True)
471 class RunConfigDiffNewTestsSerializer(serializers.Serializer):
472 test = TestSerializer()
473 statuses = serializers.DictField(child=serializers.IntegerField(read_only=True))
474 exec_time = ExecutionTimeSerializer(read_only=True)
476 @extend_schema_field(serializers.DictField(child=BuildDiff2Serializer()))
477 def get_builds(self, obj):
478 bd2 = namedtuple('BuildDiff2', ['component', 'from_build', 'to_build'])
479 build_diffs = {k: bd2(k, v.from_build, v.to_build) for k, v in obj.builds.items()}
480 dict_ser = serializers.DictField(child=self.BuildDiff2Serializer())
481 return dict_ser.to_representation(build_diffs)
483 def __statuses(self, statuses):
484 return {k.name: v for k, v in statuses.items()}
486 @extend_schema_field(serializers.DictField(child=RunConfigDiffNewTestsSerializer()))
487 def get_new_tests(self, obj):
488 NT = namedtuple('NewTest', ['test', 'statuses', 'exec_time'])
489 new_tests = {k: NT(k, {k.name: v for k, v in v.to_statuses.items()}, v.to_exec_times)
490 for k, v in obj.new_tests.tests.items()}
491 dict_ser = serializers.DictField(child=self.RunConfigDiffNewTestsSerializer())
492 return dict_ser.to_representation(new_tests)
495class ReplicationScriptSerializer(serializers.ModelSerializer):
496 created_by = serializers.StringRelatedField()
497 source_tracker = serializers.StringRelatedField()
498 destination_tracker = serializers.StringRelatedField()
500 class Meta:
501 model = ReplicationScript
502 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script')
505class KnownIssuesSerializer(serializers.Serializer):
506 id = serializers.IntegerField(read_only=True)
507 testsuite = serializers.SerializerMethodField()
508 machine = serializers.SerializerMethodField()
509 run_id = serializers.SerializerMethodField()
510 test = serializers.SerializerMethodField()
511 status = serializers.SerializerMethodField()
512 url = serializers.SerializerMethodField()
514 bugs = serializers.SerializerMethodField()
516 def __init__(self, *args, **kwargs):
517 super().__init__(*args, **kwargs)
519 # Cache the serializers for performance reasons
520 self._char_ser = serializers.CharField(max_length=255, read_only=True)
521 self._int_ser = serializers.IntegerField(read_only=True)
522 self._bug_min_ser = BugMinimalSerializer(many=True)
524 @extend_schema_field(OpenApiTypes.INT)
525 def get_run_id(self, obj):
526 return self._int_ser.to_representation(obj.result.ts_run.run_id)
528 @extend_schema_field(OpenApiTypes.STR)
529 def get_testsuite(self, obj):
530 return self._char_ser.to_representation(obj.result.test.testsuite.name)
532 @extend_schema_field(OpenApiTypes.STR)
533 def get_test(self, obj):
534 return self._char_ser.to_representation(obj.result.test.name)
536 @extend_schema_field(OpenApiTypes.STR)
537 def get_machine(self, obj):
538 return self._char_ser.to_representation(obj.result.ts_run.machine.name)
540 @extend_schema_field(OpenApiTypes.STR)
541 def get_status(self, obj):
542 return self._char_ser.to_representation(obj.result.status.name)
544 @extend_schema_field(OpenApiTypes.STR)
545 def get_url(self, obj):
546 return self._char_ser.to_representation(obj.result.url)
548 @extend_schema_field(BugMinimalSerializer)
549 def get_bugs(self, obj):
550 return self._bug_min_ser.to_representation(obj.matched_ifa.issue.bugs.all())
553class BugTrackerAccountSerializer(serializers.ModelSerializer):
554 class Meta:
555 model = BugTrackerAccount
556 fields = ('id', 'is_developer')
557 read_only_fields = ('id', )
560class ShortenerSerializer(serializers.ModelSerializer):
561 class Meta:
562 model = Shortener
563 fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
564 read_only_fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
567class RateSerializer(serializers.Serializer):
568 count = serializers.IntegerField()
569 total = serializers.IntegerField()
570 percent = serializers.FloatField()
573class BugSerializer(serializers.ModelSerializer):
574 class Meta:
575 model = Bug
576 fields = ('short_name', 'title', 'url')
577 read_only_fields = ('short_name', 'title', 'url')
580class RestIssueSerializer(serializers.ModelSerializer):
581 class Meta:
582 model = Issue
583 fields = ('__all__')
584 read_only_fields = ('id', 'added_on', 'archived_on', 'runconfigs_covered_count', 'runconfigs_affected_count',
585 'last_seen', 'added_by', 'archived_by', 'last_seen_runconfig')
587 bugs = BugSerializer(many=True)
590class IssueSerializer(serializers.ModelSerializer):
591 class Meta:
592 model = Issue
593 fields = ('id', 'bugs', 'expected')
594 read_only_fields = ('id', 'bugs', 'expected')
596 bugs = BugSerializer(many=True)
599class IssueSerializerMinimal(serializers.ModelSerializer):
600 class Meta:
601 model = Issue
602 fields = ('id', )
603 read_only_fields = ('id', )
606def serialize_issue_hitrate(issues, minimal=False):
607 Serializer = IssueSerializerMinimal if minimal else IssueSerializer
609 ret = []
610 for issue, rate in issues.items():
611 val = Serializer(issue).data
612 val['hit_rate'] = RateSerializer(rate).data
613 ret.append(val)
614 return ret
617def serialize_MetricPassRatePerRunconfig(history):
618 runconfigs = OrderedDict()
619 for runconfig, _statuses in history.runconfigs.items():
620 runconfigs[str(runconfig)] = OrderedDict()
621 for status, rate in _statuses.items():
622 runconfigs[str(runconfig)][str(status)] = RateSerializer(rate).data
624 statuses = OrderedDict()
625 for status, _runconfigs in history.statuses.items():
626 statuses[str(status)] = OrderedDict()
627 for runconfig, rate in _runconfigs.items():
628 statuses[str(status)][str(runconfig)] = RateSerializer(rate).data
630 return {
631 "runconfigs": runconfigs,
632 "statuses": statuses,
633 "most_hit_issues": serialize_issue_hitrate(history.most_hit_issues),
634 "query_key": history.query.query_key,
635 }
638class PassRateStatisticsSerializer(serializers.Serializer):
639 passrate = RateSerializer()
640 runrate = RateSerializer()
641 discarded_rate = RateSerializer()
642 notrun_rate = RateSerializer()
645def serialize_MetricPassRatePerTest(metric_passrate):
646 discarded_status = "discarded (expected)"
648 tests = OrderedDict()
649 for test, results in metric_passrate.tests.items():
650 if results.is_fully_discarded:
651 tests[str(test)] = {
652 "status": discarded_status,
653 "is_pass": False,
654 "is_run": False,
655 "duration": str(results.duration),
656 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
657 }
658 else:
659 tests[str(test)] = {
660 "status": str(results.overall_result),
661 "is_pass": results.is_pass,
662 "is_run": not results.overall_result.is_notrun,
663 "duration": str(results.duration),
664 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
665 }
667 statuses = OrderedDict()
668 for status, rate in metric_passrate.statuses.items():
669 statuses[str(status)] = RateSerializer(rate).data
671 return {
672 "tests": tests,
673 "statuses": statuses,
674 "discarded_status": discarded_status,
675 "machines": [str(m) for m in metric_passrate.machines],
676 "runconfigs": RunConfigSerializer(metric_passrate.runconfigs, many=True).data,
677 "raw_statistics": PassRateStatisticsSerializer(metric_passrate.raw_statistics).data,
678 "statistics": PassRateStatisticsSerializer(metric_passrate.statistics).data,
679 "most_hit_issues": serialize_issue_hitrate(metric_passrate.most_hit_issues),
680 "uncovered_failure_rate": RateSerializer(metric_passrate.uncovered_failure_rate).data,
681 "notrun_rate": RateSerializer(metric_passrate.notrun_rate).data,
682 "most_interrupting_issues": serialize_issue_hitrate(metric_passrate.most_interrupting_issues),
683 "unknown_failure_interruption_rate": RateSerializer(metric_passrate.unknown_failure_interruption_rate).data,
684 "unexplained_interruption_rate": RateSerializer(metric_passrate.unexplained_interruption_rate).data,
685 "query_key": metric_passrate.query.query_key,
686 }