Coverage for CIResults / views.py: 78%

587 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-27 09:21 +0000

1from django.contrib import messages 

2from django.contrib.auth.mixins import PermissionRequiredMixin 

3from django.shortcuts import render, redirect, get_object_or_404 

4from django.core.exceptions import ValidationError, PermissionDenied 

5from django.utils.safestring import mark_safe 

6from django.views import View 

7from django.views.generic import DetailView, ListView 

8from django.views.generic.base import TemplateView 

9from django.views.generic.edit import UpdateView, FormView 

10from django.http import JsonResponse, HttpResponseRedirect 

11from django.db import transaction 

12from django.db.models import Prefetch 

13from django.urls import reverse 

14from django.utils.functional import cached_property 

15from django.core.paginator import Paginator 

16 

17from collections import namedtuple 

18 

19from .models import BugTracker, Bug, Component, Build, Test, Machine, RunConfigTag, RunConfig, TestSuite, TextStatus 

20from .models import TestResult, IssueFilter, IssueFilterAssociated, Issue, KnownFailure, UnknownFailure, MachineTag 

21 

22from .metrics import metrics_testresult_statuses_stats, metrics_testresult_machines_stats 

23from .metrics import metrics_testresult_tests_stats, metrics_testresult_issues_stats 

24from .metrics import metrics_knownfailure_statuses_stats, metrics_knownfailure_machines_stats 

25from .metrics import metrics_knownfailure_tests_stats, metrics_knownfailure_issues_stats 

26 

27from .forms import TestMassRenameForm 

28 

29from .filtering import QueryCreator, QueryParser 

30 

31import traceback 

32import markdown 

33import json 

34import re 

35 

36 

37def index(request, **kwargs): 

38 uf = UnknownFailure.objects.select_related("result").prefetch_related( 

39 "result__test", 

40 "result__test__testsuite", 

41 "result__ts_run", 

42 "result__ts_run__machine", 

43 "result__ts_run__machine__tags", 

44 "result__ts_run__runconfig", 

45 "result__ts_run__testsuite", 

46 "result__status__testsuite", 

47 "result__status", 

48 "matched_archived_ifas", 

49 "matched_archived_ifas__issue__bugs__tracker", 

50 ) 

51 uf = uf.defer("result__dmesg", "result__stdout", "result__stderr") 

52 uf = uf.filter(result__ts_run__runconfig__temporary=False).order_by('id') 

53 

54 ifas_query = IssueFilterAssociated.objects.filter(deleted_on=None).all() 

55 issues = Issue.objects.filter(archived_on=None, 

56 expected=False).prefetch_related(Prefetch('filters', 

57 to_attr='ifas_cached', 

58 queryset=ifas_query), 

59 "ifas_cached__filter", 

60 "ifas_cached__added_by", 

61 "ifas_cached__deleted_by", 

62 "bugs__assignee__person", 

63 "bugs__tracker", 

64 "bugs").order_by('-id') 

65 

66 def get_page(object_query, page, page_size=100): 

67 paginator = SafePaginator(object_query, page_size) 

68 return paginator.page(page) 

69 

70 query_get_param = request.GET.copy() 

71 

72 suppressed_tests_query = QueryCreator(request, Test, prefix="suppressed_tests").multiple_request_params_to_query() 

73 suppressed_tests = suppressed_tests_query.objects.prefetch_related('testsuite').filter(vetted_on=None) \ 

74 .exclude(first_runconfig=None).prefetch_related('first_runconfig') 

75 suppressed_tests_page = query_get_param.get('suppressed_tests', 1) 

76 

77 suppressed_machines_query = QueryCreator(request, Machine, 

78 prefix="suppressed_machine_lst").multiple_request_params_to_query() 

79 suppressed_machines = suppressed_machines_query.objects.filter(vetted_on=None) 

80 suppressed_machines_page = query_get_param.get('suppressed_machines', 1) 

81 

82 paginated_tables = { 

83 'suppressed_tests': { 

84 'table': suppressed_tests, 

85 'page': suppressed_tests_page, 

86 }, 

87 'suppressed_machines': { 

88 'table': suppressed_machines, 

89 'page': suppressed_machines_page, 

90 } 

91 } 

92 for table_name in paginated_tables: 

93 if table_name == query_get_param.get('table') and (page := query_get_param.get('page')): 

94 paginated_tables[table_name]['page'] = page 

95 paginated_tables[table_name]['table'] = get_page( 

96 paginated_tables[table_name]['table'], 

97 paginated_tables[table_name]['page'], 

98 ) 

99 query_get_param.setlist(table_name, [paginated_tables[table_name]['page']]) 

100 

101 suppressed_statuses_query = QueryCreator(request, TextStatus, 

102 prefix="suppressed_statuses_lst").multiple_request_params_to_query() 

103 suppressed_statuses = suppressed_statuses_query.objects.filter(vetted_on=None) 

104 

105 context = { 

106 "trackers": BugTracker.objects.all(), 

107 "issues": issues, 

108 "suppressed_tests": paginated_tables['suppressed_tests']['table'], 

109 "suppressed_machines": paginated_tables['suppressed_machines']['table'], 

110 "suppressed_statuses": suppressed_statuses, 

111 "unknown_failures": uf, 

112 "query_get_param": query_get_param, 

113 } 

114 return render(request, 'CIResults/index.html', context) 

115 

116 

117class SafePaginator(Paginator): 

118 def validate_number(self, number): 

119 try: 

120 number = int(number) 

121 except ValueError: 

122 number = 1 

123 return min(number, self.num_pages) 

124 

125 

126class IssueListView(ListView): 

127 model = Issue 

128 

129 def get_queryset(self): 

130 self.query = QueryCreator(self.request, self.model).request_to_query() 

131 

132 # Prefetch all the necessary things before returning the list 

133 ifas_query = IssueFilterAssociated.objects.all() 

134 issues = self.query.objects.prefetch_related(Prefetch('filters', 

135 to_attr='ifas_cached', 

136 queryset=ifas_query), 

137 "ifas_cached__filter", 

138 "ifas_cached__added_by", 

139 "ifas_cached__deleted_by", 

140 "bugs__tracker", 

141 "bugs") 

142 if self.query.orderby is None: 

143 issues = issues.order_by('-id') 

144 return issues 

145 

146 def get_context_data(self): 

147 context = super().get_context_data() 

148 context["trackers"] = BugTracker.objects.all() 

149 context['issues'] = context.pop('object_list') 

150 context['filters_model'] = Issue 

151 context['query'] = self.query 

152 return context 

153 

154 def post(self, request, *args, **kwargs): 

155 return super().get(request, *args, **kwargs) 

156 

157 

158class IssueView(View): 

159 ParsedParams = namedtuple('ParsedParams', 

160 'bugs bugs_form description error filters') 

161 

162 def __parse_params__(self, params): 

163 # Keep track of all errors, only commit if no error occured 

164 error = False 

165 

166 # Parse the bugs 

167 bugs = [] 

168 bugs_form = [] 

169 for param in params: 

170 if param.startswith("bug_id_"): 

171 # Check the bug_id 

172 bug_id = params[param].strip() 

173 if len(bug_id) == 0: 

174 continue 

175 

176 # Check the tracker 

177 suffix = param[7:] 

178 tracker_pk = params.get("bug_tracker_{}".format(suffix), -1) 

179 try: 

180 tracker_pk = int(tracker_pk) 

181 tracker = BugTracker.objects.get(pk=tracker_pk) 

182 tracker_valid = True 

183 

184 # Check the bug 

185 try: 

186 bug = Bug.objects.get(tracker=tracker, bug_id=bug_id) 

187 bugs.append(bug) 

188 bug_valid = True 

189 except Exception: 

190 # The bug does not exist, try to create it 

191 try: 

192 bug = Bug(tracker=tracker, bug_id=bug_id) 

193 bug.poll() 

194 bugs.append(bug) 

195 bug_valid = True 

196 except Exception: 

197 error = True 

198 bug_valid = False 

199 traceback.print_exc() 

200 except Exception: 

201 error = True 

202 tracker_valid = False 

203 bug_valid = False 

204 traceback.print_exc() 

205 

206 # Keep the information about the bugs, in case we need to 

207 # use it again to re-draw the page 

208 bugs_form.append({"bug_id": bug_id, "tracker_id": tracker_pk, 

209 "bug_valid": bug_valid, 

210 "tracker_valid": tracker_valid}) 

211 

212 # Parse the filters 

213 filters = [] 

214 for param in params: 

215 if param.startswith("filter_"): 

216 try: 

217 pk = int(params[param]) 

218 filters.append(IssueFilter.objects.get(pk=pk)) 

219 except Exception: 

220 error = True 

221 continue 

222 

223 # Get the description 

224 description = params.get("description", "") 

225 

226 return self.ParsedParams(bugs=bugs, bugs_form=bugs_form, 

227 description=description, error=error, 

228 filters=filters) 

229 

230 def __fetch_from_db__(self, pk): 

231 issue = get_object_or_404(Issue, pk=pk) 

232 

233 bugs = issue.bugs.all() 

234 bugs_form = [] 

235 for bug in bugs: 

236 bugs_form.append({"bug_id": bug.bug_id, "tracker_id": bug.tracker.id, 

237 "bug_valid": True, "tracker_valid": True}) 

238 

239 filters_assoc = IssueFilterAssociated.objects.filter(deleted_on=None, issue=issue) 

240 filters = [e.filter for e in filters_assoc] 

241 

242 return self.ParsedParams(bugs=bugs, bugs_form=bugs_form, 

243 description=issue.description, error=False, 

244 filters=filters) 

245 

246 @transaction.atomic 

247 def __save__(self, pk, d, user): 

248 if d.error: 

249 raise ValueError("The data provided contained error") 

250 

251 if len(d.filters) == 0: 

252 raise ValueError("No filters found") 

253 

254 if len(d.bugs) == 0: 

255 raise ValueError("No bugs found") 

256 

257 # Try to find the issue to edit, if applicable 

258 try: 

259 issue = Issue.objects.get(id=pk) 

260 

261 # Update the fields 

262 issue.description = d.description 

263 issue.save() 

264 except Issue.DoesNotExist: 

265 issue = Issue.objects.create(filer=user.email, added_by=user, 

266 description=d.description) 

267 

268 # Add the bugs and filters that were set 

269 issue.set_filters(d.filters, user) 

270 issue.set_bugs(d.bugs) 

271 

272 def __show_page__(self, request, pk, d): 

273 # Pre-allocate a line for the bug 

274 if len(d.bugs_form) == 0: 

275 d.bugs_form.append({"bug_id": "", "tracker_id": -1, 

276 "bug_valid": True, "tracker_valid": True}) 

277 

278 # find out whether we are creating or editing an issue 

279 if pk is None: 

280 save_url = reverse('CIResults-issue', kwargs={"action": 'create'}) 

281 issue_title = "New Issue" 

282 pk = "" 

283 else: 

284 save_url = reverse('CIResults-issue', kwargs={"action": "edit", 

285 "pk": pk}) 

286 issue_title = "Edit Issue {}".format(pk) 

287 

288 filters = list(IssueFilter.objects.filter(hidden=False).order_by("-id")[:20]) 

289 for f in d.filters: 

290 if f not in filters: 

291 filters.append(f) 

292 

293 # Get the list of tests referenced by this issue, then add all the ones 

294 # that have an associated first runconfig 

295 tests = set() 

296 for f in d.filters: 

297 tests.update(f.tests.all()) 

298 tests.update(Test.objects.prefetch_related("testsuite").exclude(first_runconfig=None)) 

299 

300 # Render the page 

301 context = { 

302 "issue_id": pk, 

303 "issue_title": issue_title, 

304 "tags": RunConfigTag.objects.all(), 

305 "machine_tags": MachineTag.objects.all(), 

306 "machines": Machine.objects.all(), 

307 "tests": tests, 

308 "statuses": TextStatus.objects.prefetch_related("testsuite").all(), 

309 "trackers": BugTracker.objects.all(), 

310 "filters": filters, 

311 "field_bugs": d.bugs_form, # This data may not be valid or commited yet 

312 "field_filters": d.filters, 

313 "field_description": d.description, 

314 "save_url": save_url, 

315 "filters_model": UnknownFailure, 

316 } 

317 return render(request, 'CIResults/issue.html', context) 

318 

319 def __edit_issue__(self, request, action, pk, params): 

320 if pk is not None and len(params) == 0: 

321 if not request.user.has_perm('CIResults.change_issue'): 

322 raise PermissionDenied() 

323 

324 # We are editing an issue and have not provided updates for it 

325 d = self.__fetch_from_db__(pk) 

326 return self.__show_page__(request, pk, d) 

327 else: 

328 if not request.user.has_perm('CIResults.add_issue'): 

329 raise PermissionDenied() 

330 

331 # In any other case, just display what we have, or default values 

332 d = self.__parse_params__(params) 

333 

334 # Save, if possible. Otherwise, just show the page again 

335 try: 

336 self.__save__(pk, d, request.user) 

337 return redirect('CIResults-index') 

338 except ValueError: 

339 return self.__show_page__(request, pk, d) 

340 

341 def __route_request__(self, request, kwargs, params): 

342 post_actions = ["archive", "restore", "hide", "show"] 

343 

344 action = kwargs['action'] 

345 if action in post_actions: 

346 if request.method != 'POST': 

347 raise ValidationError("The action '{}' is unsupported in a {} request".format(action, request.method)) 

348 

349 # Check permissions 

350 if not request.user.has_perm('CIResults.{}_issue'.format(action)): 

351 raise PermissionDenied() 

352 

353 issue = get_object_or_404(Issue, pk=kwargs.get('pk')) 

354 

355 if action == 'archive': 

356 issue.archive(request.user) 

357 else: 

358 getattr(issue, action)() 

359 

360 return redirect(request.META.get('HTTP_REFERER', 'CIResults-index')) 

361 else: 

362 # Action == edit or create 

363 return self.__edit_issue__(request, action, 

364 kwargs.get('pk'), params) 

365 

366 def get(self, request, *args, **kwargs): 

367 return self.__route_request__(request, kwargs, request.GET) 

368 

369 def post(self, request, *args, **kwargs): 

370 return self.__route_request__(request, kwargs, request.POST) 

371 

372 

373class IssueFilterView(View): 

374 def __update_stats__(self, stats, result): 

375 for tag in result.ts_run.runconfig.tags.all(): 

376 stats['tags'].add((tag.id, str(tag))) 

377 stats['machines'].add((result.ts_run.machine.id, str(result.ts_run.machine))) 

378 if len(result.ts_run.machine.tags_cached) == 0: 

379 stats['tagless_machines'].add((result.ts_run.machine.id, str(result.ts_run.machine))) 

380 else: 

381 for tag in result.ts_run.machine.tags_cached: 

382 stats['machine_tags'].add((tag.id, str(tag))) 

383 stats['tests'].add((result.test.id, str(result.test))) 

384 stats['statuses'].add((result.status.id, str(result.status))) 

385 

386 def __check_regexp__(self, params, field, errors): 

387 try: 

388 regexp = params.get(field, "") 

389 re.compile(regexp, re.DOTALL) 

390 return True 

391 except re.error as e: 

392 errors.append({"field": field, "msg": str(e)}) 

393 return False 

394 

395 def __parse_filter_from_params__(self, params) -> IssueFilter: 

396 filter = IssueFilter(description=params.get('description'), 

397 stdout_regex=params.get('stdout_regex', ""), 

398 stderr_regex=params.get('stderr_regex', ""), 

399 dmesg_regex=params.get('dmesg_regex', ""), 

400 user_query=params.get('user_query', "")) 

401 filter.tags_ids_cached = set(params.get("tags", [])) 

402 filter.__machines_cached__ = set(Machine.objects.filter(id__in=params.get("machines", []))) 

403 filter.__machine_tags_cached__ = set(MachineTag.objects.filter(id__in=params.get("machine_tags", []))) 

404 filter.tests_ids_cached = set(params.get("tests", [])) 

405 filter.statuses_ids_cached = set(params.get("statuses", [])) 

406 

407 # Assign for purpose of converting to user query 

408 filter.tags_cached = set(RunConfigTag.objects.filter(id__in=params.get("tags", []))) 

409 filter.tests_cached = set(Test.objects.filter(id__in=params.get("tests", []))) 

410 filter.statuses_cached = set(TextStatus.objects.filter(id__in=params.get("statuses", []))) 

411 return filter 

412 

413 def __stats__(self, request): 

414 stats = { 

415 "matched": { 

416 "tags": set(), 

417 "machine_tags": set(), 

418 "machines": set(), 

419 "tagless_machines": set(), 

420 "tests": set(), 

421 "statuses": set() 

422 }, 

423 "errors": [ 

424 ], 

425 } 

426 

427 # Read the parameters and perform some input validation 

428 params = json.loads(request.body.decode()) 

429 self.__check_regexp__(params, 'stdout_regex', stats['errors']) 

430 self.__check_regexp__(params, 'stderr_regex', stats['errors']) 

431 self.__check_regexp__(params, 'dmesg_regex', stats['errors']) 

432 

433 filter = self.__parse_filter_from_params__(params) 

434 

435 parser = QueryParser(UnknownFailure, filter.equivalent_user_query) 

436 if parser.error: 

437 stats['errors'].append({"field": "user_query", "msg": parser.error}) 

438 

439 # Check the filter 

440 if len(stats['errors']) == 0: 

441 filter = self.__parse_filter_from_params__(params) 

442 

443 # Check which failures are matched by this filter 

444 matched_failures = ( 

445 parser.objects.select_related("result") 

446 .prefetch_related( 

447 "result__ts_run__runconfig__tags", 

448 "result__ts_run__machine", 

449 "result__ts_run__machine__tags", 

450 "result__test", 

451 "result__test__testsuite", 

452 "result__status", 

453 ) 

454 .filter(result__ts_run__runconfig__temporary=False) 

455 .defer("result__dmesg", "result__stdout", "result__stderr") 

456 ) 

457 

458 for failure in matched_failures: 

459 self.__update_stats__(stats['matched'], failure.result) 

460 

461 for key in stats['matched']: 

462 stats['matched'][key] = sorted(stats['matched'][key]) 

463 stats['matched']['failure_total'] = UnknownFailure.objects.filter( 

464 result__ts_run__runconfig__temporary=False 

465 ).count() 

466 stats['matched']['failure_count'] = len(matched_failures) 

467 else: 

468 for key in stats['matched']: 

469 stats['matched'][key] = sorted(stats['matched'][key]) 

470 stats['matched']['failure_total'] = 0 

471 stats['matched']['failure_count'] = 0 

472 

473 return JsonResponse(stats) 

474 

475 def __convert_to_user_query__(self, request): 

476 params = json.loads(request.body.decode()) 

477 filter = self.__parse_filter_from_params__(params) 

478 return JsonResponse({"userQuery": filter._to_user_query()}) 

479 

480 def post(self, request, *args, **kwargs): 

481 # Technically, this view cannot edit filters, but it deals with a lot of 

482 # input and it is only useful when editing issues, so it is safer to hide it 

483 if not request.user.has_perm('CIResults.change_issue'): 

484 raise PermissionDenied() 

485 

486 action = kwargs.get('action') 

487 if action == "stats": 

488 return self.__stats__(request) 

489 if action == "convert-to-user-query": 

490 return self.__convert_to_user_query__(request) 

491 

492 raise ValidationError("The action '{}' is unsupported".format(action)) 

493 

494 

495class MassVettingView(PermissionRequiredMixin, View): 

496 def post(self, request, *args, **kwargs): 

497 object_ids = set() 

498 for param in request.POST: 

499 try: 

500 object_ids.add(int(param)) 

501 except ValueError: 

502 continue 

503 

504 objects = self.model.objects.filter(pk__in=object_ids) 

505 for obj in objects: 

506 obj.vet() 

507 

508 objects_str = [str(o) for o in objects] 

509 messages.success(self.request, "Successfully vetted the {} object(s): {}".format(len(objects), 

510 ", ".join(objects_str))) 

511 

512 return redirect('CIResults-index') 

513 

514 

515class MachineMassVettingView(MassVettingView): 

516 model = Machine 

517 permission_required = 'CIResults.vet_machine' 

518 

519 

520class TestMassVettingView(MassVettingView): 

521 model = Test 

522 permission_required = 'CIResults.vet_test' 

523 

524 

525class TextStatusMassVettingView(MassVettingView): 

526 model = TextStatus 

527 permission_required = 'CIResults.vet_textstatus' 

528 

529 

530class TestEditView(PermissionRequiredMixin, UpdateView): 

531 model = Test 

532 fields = ['public', 'vetted_on'] 

533 template_name = 'CIResults/test_edit.html' 

534 permission_required = 'CIResults.change_test' 

535 

536 def get_success_url(self): 

537 messages.success(self.request, "The test {} has been edited".format(self.object)) 

538 return reverse("CIResults-tests") 

539 

540 

541class MachineEditView(PermissionRequiredMixin, UpdateView): 

542 model = Machine 

543 fields = ['description', 'public', 'vetted_on', 'aliases', 'color_hex', 'tags'] 

544 template_name = 'CIResults/machine_edit.html' 

545 permission_required = 'CIResults.change_machine' 

546 

547 def get_success_url(self): 

548 messages.success(self.request, "The machine {} has been edited".format(self.object)) 

549 return reverse("CIResults-machines") 

550 

551 

552class TestMassRenameView(PermissionRequiredMixin, FormView): 

553 form_class = TestMassRenameForm 

554 template_name = 'CIResults/test_mass_rename.html' 

555 permission_required = 'CIResults.change_test' 

556 

557 def get_success_url(self): 

558 return reverse("CIResults-tests") 

559 

560 def form_valid(self, form): 

561 if not form.is_valid(): 

562 return super().form_valid(form) 

563 

564 if self.request.POST.get('action') == 'check': 

565 return render(self.request, self.template_name, {'form': form}) 

566 else: 

567 form.do_renaming() 

568 messages.success(self.request, 

569 "Renamed {} tests ('{}' -> '{}')".format(len(form.affected_tests), 

570 form.cleaned_data.get('substring_from'), 

571 form.cleaned_data.get('substring_to'))) 

572 return super().form_valid(form) 

573 

574 

575class TestRenameView(PermissionRequiredMixin, UpdateView): 

576 model = Test 

577 fields = ['name'] 

578 template_name = 'CIResults/test_rename.html' 

579 permission_required = 'CIResults.change_test' 

580 

581 def form_valid(self, form): 

582 # We do not want to save any change, we just want to call the test's rename method 

583 self.object.rename(form.cleaned_data['name']) 

584 messages.success(self.request, "The test {} has been renamed to {}".format(self.object, 

585 form.cleaned_data['name'])) 

586 return HttpResponseRedirect(reverse("CIResults-tests")) 

587 

588 

589class filterView(DetailView): 

590 model = IssueFilter 

591 template_name = 'CIResults/filter.html' 

592 

593 

594class SimpleSearchableMixin: 

595 @property 

596 def query(self): 

597 return self.request.GET.get('q', '') 

598 

599 # TODO: Add a nice UI for this 

600 def get_paginate_by(self, queryset): 

601 return self.request.GET.get('page_size', self.paginate_by) 

602 

603 def get_context_data(self): 

604 context = super().get_context_data() 

605 context['search_query'] = self.query 

606 context['query_get_param'] = "q=" + self.query 

607 return context 

608 

609 

610class TestListView(ListView): 

611 model = Test 

612 paginator_class = SafePaginator 

613 paginate_by = 100 

614 

615 def get_queryset(self): 

616 self.query = QueryCreator(self.request, self.model).multiple_request_params_to_query() 

617 tests = self.query.objects.order_by('testsuite__name', 'name').prefetch_related('testsuite', 

618 'first_runconfig') 

619 return tests.exclude(first_runconfig=None) 

620 

621 def get_context_data(self): 

622 context = super().get_context_data() 

623 context['query_get_param'] = self.request.GET.copy() 

624 return context 

625 

626 

627class MachineListView(ListView): 

628 model = Machine 

629 paginator_class = SafePaginator 

630 paginate_by = 100 

631 

632 def get_queryset(self): 

633 self.query = QueryCreator(self.request, self.model).multiple_request_params_to_query() 

634 machines = self.query.objects.order_by('name').prefetch_related('aliases', 'tags') 

635 

636 return machines 

637 

638 def get_context_data(self): 

639 context = super().get_context_data() 

640 context['query_get_param'] = self.request.GET.copy() 

641 return context 

642 

643 

644class UserFiltrableMixin: 

645 # TODO: Add a nice UI for this 

646 def get_paginate_by(self, queryset): 

647 return self.request.GET.get('page_size', self.paginate_by) 

648 

649 def get_context_data(self): 

650 context = super().get_context_data() 

651 context['query_get_param'] = self.request.GET.copy() 

652 context['query'] = self.query 

653 

654 return context 

655 

656 

657class TestResultListView(UserFiltrableMixin, ListView): 

658 model = TestResult 

659 paginator_class = SafePaginator 

660 paginate_by = 100 

661 

662 def get_userquery(self): 

663 return None 

664 

665 def get_queryset(self): 

666 if self.get_userquery() is None: 

667 self.query = QueryCreator(self.request, self.model).request_to_query() 

668 else: 

669 self.query = self.model.from_user_filters(**self.get_userquery()) 

670 

671 if not self.query.is_empty: 

672 query = self.query.objects 

673 query = query.filter(ts_run__runconfig__temporary=False) # Do not show results from temp runs 

674 query = query.prefetch_related('test', 

675 'test__testsuite', 

676 'ts_run', 

677 'ts_run__machine', 

678 'ts_run__machine__tags', 

679 'ts_run__runconfig', 

680 'ts_run__testsuite', 

681 'status', 

682 'status__testsuite', 

683 'known_failures__matched_ifa__filter', 

684 'known_failures__matched_ifa__issue__bugs__tracker') 

685 query = query.defer('stdout', 'stderr', 'dmesg') 

686 if self.query.orderby is None: 

687 query = query.order_by('-ts_run__runconfig__added_on') 

688 return query 

689 else: 

690 return self.model.objects.none().order_by('id') 

691 

692 def get_context_data(self): 

693 context = super().get_context_data() 

694 context['results'] = context.pop('object_list') 

695 

696 context["statuses_chart"] = metrics_testresult_statuses_stats(context['results']).stats() 

697 context["machines_chart"] = metrics_testresult_machines_stats(context['results']).stats() 

698 context["tests_chart"] = metrics_testresult_tests_stats(context['results']).stats() 

699 context["issues_chart"] = metrics_testresult_issues_stats(context['results']).stats() 

700 

701 if self.get_userquery() is not None: 

702 context['filter_url'] = reverse('CIResults-results') 

703 

704 return context 

705 

706 def post(self, request, *args, **kwargs): 

707 return super().get(request, *args, **kwargs) 

708 

709 

710class KnownFailureListView(UserFiltrableMixin, ListView): 

711 model = KnownFailure 

712 paginator_class = SafePaginator 

713 paginate_by = 100 

714 

715 def get_userquery(self): 

716 return None 

717 

718 def get_queryset(self): 

719 if self.get_userquery() is None: 

720 self.query = QueryCreator(self.request, self.model).request_to_query() 

721 else: 

722 self.query = self.model.from_user_filters(**self.get_userquery()) 

723 

724 if not self.query.is_empty: 

725 query = self.query.objects 

726 query = query.filter(result__ts_run__runconfig__temporary=False) # Do not show results from temp runs 

727 query = query.prefetch_related('result__test', 

728 'result__test__testsuite', 

729 'result__ts_run', 

730 'result__ts_run__machine', 

731 'result__ts_run__runconfig', 

732 'result__ts_run__testsuite', 

733 'result__status', 

734 'result__status__testsuite', 

735 'matched_ifa__filter', 

736 'matched_ifa__issue__bugs__tracker') 

737 if self.query.orderby is None: 

738 query = query.order_by('-result__ts_run__runconfig__added_on') 

739 return query 

740 else: 

741 return self.model.objects.none().order_by('id') 

742 

743 def get_context_data(self): 

744 context = super().get_context_data() 

745 context['failures'] = context.pop('object_list') 

746 

747 context["statuses_chart"] = metrics_knownfailure_statuses_stats(context['failures']).stats() 

748 context["machines_chart"] = metrics_knownfailure_machines_stats(context['failures']).stats() 

749 context["tests_chart"] = metrics_knownfailure_tests_stats(context['failures']).stats() 

750 context["issues_chart"] = metrics_knownfailure_issues_stats(context['failures']).stats() 

751 

752 if self.get_userquery() is not None: 

753 context['filter_url'] = reverse('CIResults-knownfailures') 

754 

755 return context 

756 

757 def post(self, request, *args, **kwargs): 

758 return super().get(request, *args, **kwargs) 

759 

760 

761class IssueDetailView(KnownFailureListView): 

762 template_name = 'CIResults/issue_detail.html' 

763 

764 @cached_property 

765 def issue(self): 

766 return get_object_or_404(Issue, pk=self.kwargs['pk']) 

767 

768 def get_userquery(self): 

769 return {'query': "issue_id={}".format(self.issue.id)} 

770 

771 def get_context_data(self): 

772 context = super().get_context_data() 

773 context['issue'] = self.issue 

774 return context 

775 

776 

777class IFADetailView(KnownFailureListView): 

778 template_name = 'CIResults/ifa_detail.html' 

779 

780 @cached_property 

781 def ifa(self): 

782 return get_object_or_404(IssueFilterAssociated, pk=self.kwargs['pk']) 

783 

784 def get_userquery(self): 

785 return {'query': "ifa_id={}".format(self.ifa.id)} 

786 

787 def get_context_data(self): 

788 context = super().get_context_data() 

789 context['ifa'] = self.ifa 

790 return context 

791 

792 

793class TestSuiteDetailView(KnownFailureListView): 

794 template_name = 'CIResults/testsuite_detail.html' 

795 

796 @cached_property 

797 def testsuite(self): 

798 return get_object_or_404(TestSuite, pk=self.kwargs['pk']) 

799 

800 def get_userquery(self): 

801 return {'query': "testsuite_name='{}'".format(self.testsuite.name)} 

802 

803 def get_context_data(self): 

804 context = super().get_context_data() 

805 context['testsuite'] = self.testsuite 

806 return context 

807 

808 

809class TestDetailView(KnownFailureListView): 

810 template_name = 'CIResults/test_detail.html' 

811 

812 @cached_property 

813 def test(self): 

814 return get_object_or_404(Test, pk=self.kwargs['pk']) 

815 

816 def get_userquery(self): 

817 return {'query': "test_name='{}'".format(self.test.name)} 

818 

819 def get_context_data(self): 

820 context = super().get_context_data() 

821 context['test'] = self.test 

822 return context 

823 

824 

825class TextStatusDetailView(KnownFailureListView): 

826 template_name = 'CIResults/textstatus_detail.html' 

827 

828 @cached_property 

829 def status(self): 

830 return get_object_or_404(TextStatus, pk=self.kwargs['pk']) 

831 

832 def get_userquery(self): 

833 return {'query': "status_name='{}'".format(self.status.name)} 

834 

835 def get_context_data(self): 

836 context = super().get_context_data() 

837 context['status'] = self.status 

838 return context 

839 

840 

841class MachineDetailView(KnownFailureListView): 

842 template_name = 'CIResults/machine_detail.html' 

843 

844 @cached_property 

845 def machine(self): 

846 return get_object_or_404(Machine, pk=self.kwargs['pk']) 

847 

848 def get_userquery(self): 

849 return {'query': "machine_name='{}'".format(self.machine.name)} 

850 

851 def get_context_data(self): 

852 context = super().get_context_data() 

853 context['machine'] = self.machine 

854 return context 

855 

856 

857class TestResultDetailView(TestResultListView): 

858 template_name = 'CIResults/testresult_detail.html' 

859 

860 @cached_property 

861 def testresult(self): 

862 return get_object_or_404(TestResult, pk=self.kwargs['pk']) 

863 

864 def get_userquery(self): 

865 return {'query': "test_name='{}' AND machine_name='{}'".format(self.testresult.test.name, 

866 self.testresult.ts_run.machine.name)} 

867 

868 def get_context_data(self): 

869 context = super().get_context_data() 

870 context['testresult'] = self.testresult 

871 return context 

872 

873 

874class RunConfigDetailView(KnownFailureListView): 

875 template_name = 'CIResults/runconfig_detail.html' 

876 

877 @cached_property 

878 def runconfig(self): 

879 return get_object_or_404(RunConfig, pk=self.kwargs['pk']) 

880 

881 def get_userquery(self): 

882 return {'query': "runconfig_name='{}'".format(self.runconfig.name)} 

883 

884 def get_context_data(self): 

885 context = super().get_context_data() 

886 context['runconfig'] = self.runconfig 

887 return context 

888 

889 

890class RunConfigTagDetailView(KnownFailureListView): 

891 template_name = 'CIResults/runconfig_tag_detail.html' 

892 

893 @cached_property 

894 def tag(self): 

895 return get_object_or_404(RunConfigTag, pk=self.kwargs['pk']) 

896 

897 def get_userquery(self): 

898 return {'query': "runconfig_tag='{}'".format(self.tag.name)} 

899 

900 def get_context_data(self): 

901 context = super().get_context_data() 

902 context['tag'] = self.tag 

903 return context 

904 

905 

906class BuildDetailView(KnownFailureListView): 

907 template_name = 'CIResults/build_detail.html' 

908 

909 @cached_property 

910 def build(self): 

911 return get_object_or_404(Build, pk=self.kwargs['pk']) 

912 

913 def get_userquery(self): 

914 return {'query': "build_name='{}'".format(self.build.name)} 

915 

916 def get_context_data(self): 

917 context = super().get_context_data() 

918 context['build'] = self.build 

919 return context 

920 

921 

922class ComponentDetailView(KnownFailureListView): 

923 template_name = 'CIResults/component_detail.html' 

924 

925 @cached_property 

926 def component(self): 

927 return get_object_or_404(Component, pk=self.kwargs['pk']) 

928 

929 def get_userquery(self): 

930 return {'query': "component_name='{}'".format(self.component.name)} 

931 

932 def get_context_data(self): 

933 context = super().get_context_data() 

934 context['component'] = self.component 

935 return context 

936 

937 

938class ResultsCompareView(TemplateView): 

939 template_name = "CIResults/compare_results.html" 

940 

941 def name_to_runconfig(self, name): 

942 if name is None: 

943 return None, False 

944 

945 run = RunConfig.objects.filter(name=name).first() 

946 return run, run is None 

947 

948 def urlify(self, markdown): 

949 regex = re.compile(r'(https?:\/\/[^\s,;]+)') 

950 return regex.sub(r'<\1>', markdown) 

951 

952 def get_context_data(self): 

953 run_from, from_error = self.name_to_runconfig(self.request.GET.get('from')) 

954 run_to, to_error = self.name_to_runconfig(self.request.GET.get('to')) 

955 no_compress = self.request.GET.get('nocompress') is not None 

956 query = QueryCreator(self.request, TestResult).request_to_query() 

957 

958 context = super().get_context_data() 

959 context['runconfigs'] = RunConfig.objects.filter(temporary=False).order_by('-added_on')[0:50] 

960 context['from_error'] = from_error 

961 context['to_error'] = to_error 

962 context['no_compress'] = no_compress 

963 context['filters_model'] = TestResult 

964 context['query'] = query 

965 

966 if run_from and run_to: 

967 diff = run_from.compare(run_to, no_compress=no_compress, query=query) 

968 context['diff'] = diff 

969 context['html_result'] = mark_safe(markdown.markdown(self.urlify(diff.text), 

970 extensions=['nl2br'])) 

971 

972 return context