Coverage for CIResults/views.py: 78%

587 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-19 09:20 +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_bugs(d.bugs) 

270 issue.set_filters(d.filters, user) 

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

679 'ts_run__testsuite', 

680 'status', 

681 'status__testsuite', 

682 'known_failures__matched_ifa__filter', 

683 'known_failures__matched_ifa__issue__bugs__tracker') 

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

685 if self.query.orderby is None: 

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

687 return query 

688 else: 

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

690 

691 def get_context_data(self): 

692 context = super().get_context_data() 

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

694 

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

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

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

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

699 

700 if self.get_userquery() is not None: 

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

702 

703 return context 

704 

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

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

707 

708 

709class KnownFailureListView(UserFiltrableMixin, ListView): 

710 model = KnownFailure 

711 paginator_class = SafePaginator 

712 paginate_by = 100 

713 

714 def get_userquery(self): 

715 return None 

716 

717 def get_queryset(self): 

718 if self.get_userquery() is None: 

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

720 else: 

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

722 

723 if not self.query.is_empty: 

724 query = self.query.objects 

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

726 query = query.prefetch_related('result__test', 

727 'result__test__testsuite', 

728 'result__ts_run', 

729 'result__ts_run__machine', 

730 'result__ts_run__runconfig', 

731 'result__ts_run__testsuite', 

732 'result__status', 

733 'result__status__testsuite', 

734 'matched_ifa__filter', 

735 'matched_ifa__issue__bugs__tracker') 

736 if self.query.orderby is None: 

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

738 return query 

739 else: 

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

741 

742 def get_context_data(self): 

743 context = super().get_context_data() 

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

745 

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

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

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

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

750 

751 if self.get_userquery() is not None: 

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

753 

754 return context 

755 

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

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

758 

759 

760class IssueDetailView(KnownFailureListView): 

761 template_name = 'CIResults/issue_detail.html' 

762 

763 @cached_property 

764 def issue(self): 

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

766 

767 def get_userquery(self): 

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

769 

770 def get_context_data(self): 

771 context = super().get_context_data() 

772 context['issue'] = self.issue 

773 return context 

774 

775 

776class IFADetailView(KnownFailureListView): 

777 template_name = 'CIResults/ifa_detail.html' 

778 

779 @cached_property 

780 def ifa(self): 

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

782 

783 def get_userquery(self): 

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

785 

786 def get_context_data(self): 

787 context = super().get_context_data() 

788 context['ifa'] = self.ifa 

789 return context 

790 

791 

792class TestSuiteDetailView(KnownFailureListView): 

793 template_name = 'CIResults/testsuite_detail.html' 

794 

795 @cached_property 

796 def testsuite(self): 

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

798 

799 def get_userquery(self): 

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

801 

802 def get_context_data(self): 

803 context = super().get_context_data() 

804 context['testsuite'] = self.testsuite 

805 return context 

806 

807 

808class TestDetailView(KnownFailureListView): 

809 template_name = 'CIResults/test_detail.html' 

810 

811 @cached_property 

812 def test(self): 

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

814 

815 def get_userquery(self): 

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

817 

818 def get_context_data(self): 

819 context = super().get_context_data() 

820 context['test'] = self.test 

821 return context 

822 

823 

824class TextStatusDetailView(KnownFailureListView): 

825 template_name = 'CIResults/textstatus_detail.html' 

826 

827 @cached_property 

828 def status(self): 

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

830 

831 def get_userquery(self): 

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

833 

834 def get_context_data(self): 

835 context = super().get_context_data() 

836 context['status'] = self.status 

837 return context 

838 

839 

840class MachineDetailView(KnownFailureListView): 

841 template_name = 'CIResults/machine_detail.html' 

842 

843 @cached_property 

844 def machine(self): 

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

846 

847 def get_userquery(self): 

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

849 

850 def get_context_data(self): 

851 context = super().get_context_data() 

852 context['machine'] = self.machine 

853 return context 

854 

855 

856class TestResultDetailView(TestResultListView): 

857 template_name = 'CIResults/testresult_detail.html' 

858 

859 @cached_property 

860 def testresult(self): 

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

862 

863 def get_userquery(self): 

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

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

866 

867 def get_context_data(self): 

868 context = super().get_context_data() 

869 context['testresult'] = self.testresult 

870 return context 

871 

872 

873class RunConfigDetailView(KnownFailureListView): 

874 template_name = 'CIResults/runconfig_detail.html' 

875 

876 @cached_property 

877 def runconfig(self): 

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

879 

880 def get_userquery(self): 

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

882 

883 def get_context_data(self): 

884 context = super().get_context_data() 

885 context['runconfig'] = self.runconfig 

886 return context 

887 

888 

889class RunConfigTagDetailView(KnownFailureListView): 

890 template_name = 'CIResults/runconfig_tag_detail.html' 

891 

892 @cached_property 

893 def tag(self): 

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

895 

896 def get_userquery(self): 

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

898 

899 def get_context_data(self): 

900 context = super().get_context_data() 

901 context['tag'] = self.tag 

902 return context 

903 

904 

905class BuildDetailView(KnownFailureListView): 

906 template_name = 'CIResults/build_detail.html' 

907 

908 @cached_property 

909 def build(self): 

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

911 

912 def get_userquery(self): 

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

914 

915 def get_context_data(self): 

916 context = super().get_context_data() 

917 context['build'] = self.build 

918 return context 

919 

920 

921class ComponentDetailView(KnownFailureListView): 

922 template_name = 'CIResults/component_detail.html' 

923 

924 @cached_property 

925 def component(self): 

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

927 

928 def get_userquery(self): 

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

930 

931 def get_context_data(self): 

932 context = super().get_context_data() 

933 context['component'] = self.component 

934 return context 

935 

936 

937class ResultsCompareView(TemplateView): 

938 template_name = "CIResults/compare_results.html" 

939 

940 def name_to_runconfig(self, name): 

941 if name is None: 

942 return None, False 

943 

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

945 return run, run is None 

946 

947 def urlify(self, markdown): 

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

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

950 

951 def get_context_data(self): 

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

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

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

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

956 

957 context = super().get_context_data() 

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

959 context['from_error'] = from_error 

960 context['to_error'] = to_error 

961 context['no_compress'] = no_compress 

962 context['filters_model'] = TestResult 

963 context['query'] = query 

964 

965 if run_from and run_to: 

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

967 context['diff'] = diff 

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

969 extensions=['nl2br'])) 

970 

971 return context