Coverage for CIResults / serializers.py: 89%
418 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 09:21 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 09:21 +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 resp = {
391 'url': bug.url,
392 'bug_id': bug.bug_id,
393 'title': bug.title,
394 'description': bug.description,
395 'tracker': str(bug.tracker),
396 'created': _date_formatter(bug.created),
397 'updated': _date_formatter(bug.updated),
398 'polled': _date_formatter(bug.polled),
399 'closed': _date_formatter(bug.closed),
400 'creator': str(bug.creator),
401 'assignee': str(bug.assignee),
402 'product': bug.product,
403 'component': bug.component,
404 'priority': bug.priority,
405 'severity': bug.severity,
406 'features': bug.features_list,
407 'platforms': bug.platforms_list,
408 'status': bug.status,
409 'tags': bug.tags_list,
410 'custom_fields': bug.custom_fields,
411 'new_comments': []
412 }
413 if new_comments:
414 for comm in new_comments:
415 person = comm.db_object.account.person
416 author = person.full_name if person.full_name else person.email
417 resp['new_comments'].append({'author': author,
418 'created': str(comm.db_object.created_on),
419 'body': comm.body})
420 return resp
423class BugCompleteSerializer(serializers.ModelSerializer):
424 tracker = BugTrackerSerializer()
426 class Meta:
427 model = Bug
428 fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
429 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
430 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
431 'flagged_as_update_pending_on', 'custom_fields')
432 read_only_fields = ('id', 'tracker', 'bug_id', 'parent', 'children', 'title', 'description', 'created',
433 'updated', 'polled', 'closed', 'creator', 'assignee', 'product', 'component',
434 'priority', 'severity', 'features', 'platforms', 'status', 'tags', 'comments_polled',
435 'flagged_as_update_pending_on', 'custom_fields')
438class BugMinimalSerializer(serializers.ModelSerializer):
439 class Meta:
440 model = Bug
441 fields = ('id', 'short_name', 'url')
442 read_only_fields = ('id', 'short_name', 'url')
445class ExecutionTimeSerializer(serializers.Serializer):
446 minimum = serializers.DurationField(read_only=True)
447 maximum = serializers.DurationField(read_only=True)
448 count = serializers.IntegerField(read_only=True)
451class RunConfigDiffSerializer(serializers.Serializer):
452 runcfg_from = RunConfigSerializer(read_only=True)
453 runcfg_to = RunConfigSerializer(read_only=True)
454 results = RunConfigResultsDiffSerializer(read_only=True, many=True)
455 new_tests = serializers.SerializerMethodField()
456 builds = serializers.SerializerMethodField()
457 bugs = BugMinimalSerializer(many=True)
458 status = serializers.CharField(max_length=10, read_only=True)
459 text = serializers.CharField(max_length=16000, read_only=True)
461 class BuildDiff2Serializer(serializers.Serializer):
462 component = ComponentSerializer(read_only=True)
463 from_build = BuildMinimalSerializer(read_only=True)
464 to_build = BuildMinimalSerializer(read_only=True)
466 class RunConfigDiffNewTestsSerializer(serializers.Serializer):
467 test = TestSerializer()
468 statuses = serializers.DictField(child=serializers.IntegerField(read_only=True))
469 exec_time = ExecutionTimeSerializer(read_only=True)
471 @extend_schema_field(serializers.DictField(child=BuildDiff2Serializer()))
472 def get_builds(self, obj):
473 bd2 = namedtuple('BuildDiff2', ['component', 'from_build', 'to_build'])
474 build_diffs = {k: bd2(k, v.from_build, v.to_build) for k, v in obj.builds.items()}
475 dict_ser = serializers.DictField(child=self.BuildDiff2Serializer())
476 return dict_ser.to_representation(build_diffs)
478 def __statuses(self, statuses):
479 return {k.name: v for k, v in statuses.items()}
481 @extend_schema_field(serializers.DictField(child=RunConfigDiffNewTestsSerializer()))
482 def get_new_tests(self, obj):
483 NT = namedtuple('NewTest', ['test', 'statuses', 'exec_time'])
484 new_tests = {k: NT(k, {k.name: v for k, v in v.to_statuses.items()}, v.to_exec_times)
485 for k, v in obj.new_tests.tests.items()}
486 dict_ser = serializers.DictField(child=self.RunConfigDiffNewTestsSerializer())
487 return dict_ser.to_representation(new_tests)
490class ReplicationScriptSerializer(serializers.ModelSerializer):
491 created_by = serializers.StringRelatedField()
492 source_tracker = serializers.StringRelatedField()
493 destination_tracker = serializers.StringRelatedField()
495 class Meta:
496 model = ReplicationScript
497 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script')
500class KnownIssuesSerializer(serializers.Serializer):
501 id = serializers.IntegerField(read_only=True)
502 testsuite = serializers.SerializerMethodField()
503 machine = serializers.SerializerMethodField()
504 run_id = serializers.SerializerMethodField()
505 test = serializers.SerializerMethodField()
506 status = serializers.SerializerMethodField()
507 url = serializers.SerializerMethodField()
509 bugs = serializers.SerializerMethodField()
511 def __init__(self, *args, **kwargs):
512 super().__init__(*args, **kwargs)
514 # Cache the serializers for performance reasons
515 self._char_ser = serializers.CharField(max_length=255, read_only=True)
516 self._int_ser = serializers.IntegerField(read_only=True)
517 self._bug_min_ser = BugMinimalSerializer(many=True)
519 @extend_schema_field(OpenApiTypes.INT)
520 def get_run_id(self, obj):
521 return self._int_ser.to_representation(obj.result.ts_run.run_id)
523 @extend_schema_field(OpenApiTypes.STR)
524 def get_testsuite(self, obj):
525 return self._char_ser.to_representation(obj.result.test.testsuite.name)
527 @extend_schema_field(OpenApiTypes.STR)
528 def get_test(self, obj):
529 return self._char_ser.to_representation(obj.result.test.name)
531 @extend_schema_field(OpenApiTypes.STR)
532 def get_machine(self, obj):
533 return self._char_ser.to_representation(obj.result.ts_run.machine.name)
535 @extend_schema_field(OpenApiTypes.STR)
536 def get_status(self, obj):
537 return self._char_ser.to_representation(obj.result.status.name)
539 @extend_schema_field(OpenApiTypes.STR)
540 def get_url(self, obj):
541 return self._char_ser.to_representation(obj.result.url)
543 @extend_schema_field(BugMinimalSerializer)
544 def get_bugs(self, obj):
545 return self._bug_min_ser.to_representation(obj.matched_ifa.issue.bugs.all())
548class BugTrackerAccountSerializer(serializers.ModelSerializer):
549 class Meta:
550 model = BugTrackerAccount
551 fields = ('id', 'is_developer')
552 read_only_fields = ('id', )
555class ShortenerSerializer(serializers.ModelSerializer):
556 class Meta:
557 model = Shortener
558 fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
559 read_only_fields = ('id', 'shorthand', 'full', 'added_on', 'last_accessed')
562class RateSerializer(serializers.Serializer):
563 count = serializers.IntegerField()
564 total = serializers.IntegerField()
565 percent = serializers.FloatField()
568class BugSerializer(serializers.ModelSerializer):
569 class Meta:
570 model = Bug
571 fields = ('short_name', 'title', 'url')
572 read_only_fields = ('short_name', 'title', 'url')
575class RestIssueSerializer(serializers.ModelSerializer):
576 class Meta:
577 model = Issue
578 fields = ('__all__')
579 read_only_fields = ('id', 'added_on', 'archived_on', 'runconfigs_covered_count', 'runconfigs_affected_count',
580 'last_seen', 'added_by', 'archived_by', 'last_seen_runconfig')
582 bugs = BugSerializer(many=True)
585class IssueSerializer(serializers.ModelSerializer):
586 class Meta:
587 model = Issue
588 fields = ('id', 'bugs', 'expected')
589 read_only_fields = ('id', 'bugs', 'expected')
591 bugs = BugSerializer(many=True)
594class IssueSerializerMinimal(serializers.ModelSerializer):
595 class Meta:
596 model = Issue
597 fields = ('id', )
598 read_only_fields = ('id', )
601def serialize_issue_hitrate(issues, minimal=False):
602 Serializer = IssueSerializerMinimal if minimal else IssueSerializer
604 ret = []
605 for issue, rate in issues.items():
606 val = Serializer(issue).data
607 val['hit_rate'] = RateSerializer(rate).data
608 ret.append(val)
609 return ret
612def serialize_MetricPassRatePerRunconfig(history):
613 runconfigs = OrderedDict()
614 for runconfig, _statuses in history.runconfigs.items():
615 runconfigs[str(runconfig)] = OrderedDict()
616 for status, rate in _statuses.items():
617 runconfigs[str(runconfig)][str(status)] = RateSerializer(rate).data
619 statuses = OrderedDict()
620 for status, _runconfigs in history.statuses.items():
621 statuses[str(status)] = OrderedDict()
622 for runconfig, rate in _runconfigs.items():
623 statuses[str(status)][str(runconfig)] = RateSerializer(rate).data
625 return {
626 "runconfigs": runconfigs,
627 "statuses": statuses,
628 "most_hit_issues": serialize_issue_hitrate(history.most_hit_issues),
629 "query_key": history.query.query_key,
630 }
633class PassRateStatisticsSerializer(serializers.Serializer):
634 passrate = RateSerializer()
635 runrate = RateSerializer()
636 discarded_rate = RateSerializer()
637 notrun_rate = RateSerializer()
640def serialize_MetricPassRatePerTest(metric_passrate):
641 discarded_status = "discarded (expected)"
643 tests = OrderedDict()
644 for test, results in metric_passrate.tests.items():
645 if results.is_fully_discarded:
646 tests[str(test)] = {
647 "status": discarded_status,
648 "is_pass": False,
649 "is_run": False,
650 "duration": str(results.duration),
651 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
652 }
653 else:
654 tests[str(test)] = {
655 "status": str(results.overall_result),
656 "is_pass": results.is_pass,
657 "is_run": not results.overall_result.is_notrun,
658 "duration": str(results.duration),
659 "issues_hit": serialize_issue_hitrate(results.issue_occurence_rates, minimal=True),
660 }
662 statuses = OrderedDict()
663 for status, rate in metric_passrate.statuses.items():
664 statuses[str(status)] = RateSerializer(rate).data
666 return {
667 "tests": tests,
668 "statuses": statuses,
669 "discarded_status": discarded_status,
670 "machines": [str(m) for m in metric_passrate.machines],
671 "runconfigs": RunConfigSerializer(metric_passrate.runconfigs, many=True).data,
672 "raw_statistics": PassRateStatisticsSerializer(metric_passrate.raw_statistics).data,
673 "statistics": PassRateStatisticsSerializer(metric_passrate.statistics).data,
674 "most_hit_issues": serialize_issue_hitrate(metric_passrate.most_hit_issues),
675 "uncovered_failure_rate": RateSerializer(metric_passrate.uncovered_failure_rate).data,
676 "notrun_rate": RateSerializer(metric_passrate.notrun_rate).data,
677 "most_interrupting_issues": serialize_issue_hitrate(metric_passrate.most_interrupting_issues),
678 "unknown_failure_interruption_rate": RateSerializer(metric_passrate.unknown_failure_interruption_rate).data,
679 "unexplained_interruption_rate": RateSerializer(metric_passrate.unexplained_interruption_rate).data,
680 "query_key": metric_passrate.query.query_key,
681 }