Coverage for CIResults / serializers.py: 89%

418 statements  

« 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 

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

421 

422 

423class BugCompleteSerializer(serializers.ModelSerializer): 

424 tracker = BugTrackerSerializer() 

425 

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

436 

437 

438class BugMinimalSerializer(serializers.ModelSerializer): 

439 class Meta: 

440 model = Bug 

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

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

443 

444 

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) 

449 

450 

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) 

460 

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) 

465 

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) 

470 

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) 

477 

478 def __statuses(self, statuses): 

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

480 

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) 

488 

489 

490class ReplicationScriptSerializer(serializers.ModelSerializer): 

491 created_by = serializers.StringRelatedField() 

492 source_tracker = serializers.StringRelatedField() 

493 destination_tracker = serializers.StringRelatedField() 

494 

495 class Meta: 

496 model = ReplicationScript 

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

498 

499 

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

508 

509 bugs = serializers.SerializerMethodField() 

510 

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

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

513 

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) 

518 

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) 

522 

523 @extend_schema_field(OpenApiTypes.STR) 

524 def get_testsuite(self, obj): 

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

526 

527 @extend_schema_field(OpenApiTypes.STR) 

528 def get_test(self, obj): 

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

530 

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) 

534 

535 @extend_schema_field(OpenApiTypes.STR) 

536 def get_status(self, obj): 

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

538 

539 @extend_schema_field(OpenApiTypes.STR) 

540 def get_url(self, obj): 

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

542 

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

546 

547 

548class BugTrackerAccountSerializer(serializers.ModelSerializer): 

549 class Meta: 

550 model = BugTrackerAccount 

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

552 read_only_fields = ('id', ) 

553 

554 

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

560 

561 

562class RateSerializer(serializers.Serializer): 

563 count = serializers.IntegerField() 

564 total = serializers.IntegerField() 

565 percent = serializers.FloatField() 

566 

567 

568class BugSerializer(serializers.ModelSerializer): 

569 class Meta: 

570 model = Bug 

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

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

573 

574 

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

581 

582 bugs = BugSerializer(many=True) 

583 

584 

585class IssueSerializer(serializers.ModelSerializer): 

586 class Meta: 

587 model = Issue 

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

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

590 

591 bugs = BugSerializer(many=True) 

592 

593 

594class IssueSerializerMinimal(serializers.ModelSerializer): 

595 class Meta: 

596 model = Issue 

597 fields = ('id', ) 

598 read_only_fields = ('id', ) 

599 

600 

601def serialize_issue_hitrate(issues, minimal=False): 

602 Serializer = IssueSerializerMinimal if minimal else IssueSerializer 

603 

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 

610 

611 

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 

618 

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 

624 

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 } 

631 

632 

633class PassRateStatisticsSerializer(serializers.Serializer): 

634 passrate = RateSerializer() 

635 runrate = RateSerializer() 

636 discarded_rate = RateSerializer() 

637 notrun_rate = RateSerializer() 

638 

639 

640def serialize_MetricPassRatePerTest(metric_passrate): 

641 discarded_status = "discarded (expected)" 

642 

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 } 

661 

662 statuses = OrderedDict() 

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

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

665 

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 }