Coverage for CIResults/serializers.py: 89%

419 statements  

« 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 

5 

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 

10 

11from shortener.models import Shortener 

12from drf_spectacular.utils import extend_schema_field 

13from drf_spectacular.types import OpenApiTypes 

14 

15 

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.") 

29 

30 def to_representation(self, value): 

31 return value.total_seconds() 

32 

33 

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) 

38 

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 

43 

44 return kwargs 

45 

46 

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 = [] 

54 

55 def __init__(self, *args, **kwargs): 

56 extra_fields_to_serialize = kwargs.pop('extra_fields', None) 

57 super().__init__(*args, **kwargs) 

58 

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) 

66 

67 @classmethod 

68 def extra_fields(cls) -> list[str]: 

69 return list(set(cls.Meta.fields) - set(cls.default_fields)) 

70 

71 

72class RunConfigTagSerializer(serializers.ModelSerializer): 

73 class Meta: 

74 model = RunConfigTag 

75 fields = ('id', 'description', 'url', 'public', '__str__') 

76 read_only_fields = ('public', ) 

77 

78 

79class TestSuiteSerializer(serializers.ModelSerializer): 

80 class Meta: 

81 model = TestSuite 

82 fields = ('id', '__str__') 

83 

84 

85class TestSerializer(serializers.ModelSerializer): 

86 testsuite = TestSuiteSerializer() 

87 

88 class Meta: 

89 model = Test 

90 fields = ('id', 'name', 'testsuite', 'public', 'vetted_on', 'first_runconfig', '__str__') 

91 read_only_fields = ('public', ) 

92 

93 

94class MachineTagSerializer(serializers.ModelSerializer): 

95 class Meta: 

96 model = MachineTag 

97 fields = ('id', 'name', 'public') 

98 read_only_fields = ('added_on', ) 

99 

100 

101class MachineSerializer(serializers.ModelSerializer): 

102 class Meta: 

103 model = Machine 

104 fields = ('id', 'public', 'vetted_on', '__str__') 

105 read_only_fields = ('public', ) 

106 

107 

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) 

114 

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 } 

122 

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) 

139 

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 

150 

151 

152class TextStatusSerializer(serializers.ModelSerializer): 

153 testsuite = TestSuiteSerializer() 

154 

155 class Meta: 

156 model = TextStatus 

157 fields = ('id', 'name', 'testsuite', '__str__') 

158 

159 

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) 

166 

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', ) 

172 

173 

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()) 

177 

178 class Meta: 

179 model = RunConfig 

180 fields = ('id', 'name', 'tags', 'url', 'added_on', 'builds', 'environment', 'temporary', '__str__') 

181 read_only_fields = ('added_on', ) 

182 

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 

193 

194 

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() 

206 

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 

212 

213 

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) 

229 

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} 

237 

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} 

245 

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)) 

277 

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 } 

286 

287 

288class MinimalMachineSerializer(serializers.ModelSerializer): 

289 tags = serializers.StringRelatedField(many=True) 

290 

291 class Meta: 

292 model = Machine 

293 fields = ['name', 'tags'] 

294 

295 

296class MinimalRunConfigSerializer(serializers.ModelSerializer): 

297 tags = serializers.StringRelatedField(many=True) 

298 

299 class Meta: 

300 model = RunConfig 

301 fields = ['name', 'tags'] 

302 

303 

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"] 

314 

315 class Meta: 

316 model = UnknownFailure 

317 fields = ["id", "test", "status", "dmesg", "stdout", "stderr", "runconfig", "machine", "testsuite"] 

318 

319 

320class ComponentSerializer(serializers.ModelSerializer): 

321 class Meta: 

322 model = Component 

323 fields = ('id', 'name', 'description', 'url', 'public', '__str__') 

324 

325 

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()) 

329 

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'] 

337 

338 

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') 

344 

345 

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() 

351 

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]) 

355 

356 

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) 

363 

364 def get_testsuite(self, obj): 

365 ser = serializers.CharField(max_length=255, read_only=True) 

366 return ser.to_representation(obj.testsuite.name) 

367 

368 def get_test(self, obj): 

369 ser = serializers.CharField(max_length=255, read_only=True) 

370 return ser.to_representation(obj.test.name) 

371 

372 def get_machine(self, obj): 

373 ser = serializers.CharField(max_length=255, read_only=True) 

374 return ser.to_representation(obj.machine.name) 

375 

376 

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') 

384 

385 

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 

389 

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 

426 

427 

428class BugCompleteSerializer(serializers.ModelSerializer): 

429 tracker = BugTrackerSerializer() 

430 

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') 

441 

442 

443class BugMinimalSerializer(serializers.ModelSerializer): 

444 class Meta: 

445 model = Bug 

446 fields = ('id', 'short_name', 'url') 

447 read_only_fields = ('id', 'short_name', 'url') 

448 

449 

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) 

454 

455 

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) 

465 

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) 

470 

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) 

475 

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) 

482 

483 def __statuses(self, statuses): 

484 return {k.name: v for k, v in statuses.items()} 

485 

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) 

493 

494 

495class ReplicationScriptSerializer(serializers.ModelSerializer): 

496 created_by = serializers.StringRelatedField() 

497 source_tracker = serializers.StringRelatedField() 

498 destination_tracker = serializers.StringRelatedField() 

499 

500 class Meta: 

501 model = ReplicationScript 

502 fields = ('name', 'created_by', 'created_on', 'source_tracker', 'destination_tracker', 'script') 

503 

504 

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() 

513 

514 bugs = serializers.SerializerMethodField() 

515 

516 def __init__(self, *args, **kwargs): 

517 super().__init__(*args, **kwargs) 

518 

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) 

523 

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) 

527 

528 @extend_schema_field(OpenApiTypes.STR) 

529 def get_testsuite(self, obj): 

530 return self._char_ser.to_representation(obj.result.test.testsuite.name) 

531 

532 @extend_schema_field(OpenApiTypes.STR) 

533 def get_test(self, obj): 

534 return self._char_ser.to_representation(obj.result.test.name) 

535 

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) 

539 

540 @extend_schema_field(OpenApiTypes.STR) 

541 def get_status(self, obj): 

542 return self._char_ser.to_representation(obj.result.status.name) 

543 

544 @extend_schema_field(OpenApiTypes.STR) 

545 def get_url(self, obj): 

546 return self._char_ser.to_representation(obj.result.url) 

547 

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()) 

551 

552 

553class BugTrackerAccountSerializer(serializers.ModelSerializer): 

554 class Meta: 

555 model = BugTrackerAccount 

556 fields = ('id', 'is_developer') 

557 read_only_fields = ('id', ) 

558 

559 

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') 

565 

566 

567class RateSerializer(serializers.Serializer): 

568 count = serializers.IntegerField() 

569 total = serializers.IntegerField() 

570 percent = serializers.FloatField() 

571 

572 

573class BugSerializer(serializers.ModelSerializer): 

574 class Meta: 

575 model = Bug 

576 fields = ('short_name', 'title', 'url') 

577 read_only_fields = ('short_name', 'title', 'url') 

578 

579 

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') 

586 

587 bugs = BugSerializer(many=True) 

588 

589 

590class IssueSerializer(serializers.ModelSerializer): 

591 class Meta: 

592 model = Issue 

593 fields = ('id', 'bugs', 'expected') 

594 read_only_fields = ('id', 'bugs', 'expected') 

595 

596 bugs = BugSerializer(many=True) 

597 

598 

599class IssueSerializerMinimal(serializers.ModelSerializer): 

600 class Meta: 

601 model = Issue 

602 fields = ('id', ) 

603 read_only_fields = ('id', ) 

604 

605 

606def serialize_issue_hitrate(issues, minimal=False): 

607 Serializer = IssueSerializerMinimal if minimal else IssueSerializer 

608 

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 

615 

616 

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 

623 

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 

629 

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 } 

636 

637 

638class PassRateStatisticsSerializer(serializers.Serializer): 

639 passrate = RateSerializer() 

640 runrate = RateSerializer() 

641 discarded_rate = RateSerializer() 

642 notrun_rate = RateSerializer() 

643 

644 

645def serialize_MetricPassRatePerTest(metric_passrate): 

646 discarded_status = "discarded (expected)" 

647 

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 } 

666 

667 statuses = OrderedDict() 

668 for status, rate in metric_passrate.statuses.items(): 

669 statuses[str(status)] = RateSerializer(rate).data 

670 

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 }