Coverage for CIResults/views.py: 79%

590 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-23 13:11 +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, PageNotAnInteger, EmptyPage 

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 = Paginator(object_query, page_size) 

68 try: 

69 return paginator.page(page) 

70 except PageNotAnInteger: 

71 return paginator.page(1) 

72 except EmptyPage: 

73 return paginator.page(paginator.num_pages) 

74 

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

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

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

78 suppressed_tests_page = request.GET.get('suppressed_tests_page') 

79 suppressed_tests = get_page(suppressed_tests, suppressed_tests_page) 

80 

81 suppressed_machines_query = QueryCreator(request, Machine, 

82 prefix="suppressed_machine_lst").multiple_request_params_to_query() 

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

84 suppressed_machines_page = request.GET.get('suppressed_machines_page') 

85 suppressed_machines = get_page(suppressed_machines, suppressed_machines_page) 

86 

87 suppressed_statuses_query = QueryCreator(request, TextStatus, 

88 prefix="suppressed_statuses_lst").multiple_request_params_to_query() 

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

90 

91 query_get_param = "suppressed_tests_page={}&suppressed_machines_page={}".format(suppressed_tests_page, 

92 suppressed_machines_page) 

93 context = { 

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

95 "issues": issues, 

96 "suppressed_tests": suppressed_tests, 

97 "suppressed_machines": suppressed_machines, 

98 "suppressed_statuses": suppressed_statuses, 

99 "unknown_failures": uf, 

100 "query_get_param": query_get_param, 

101 } 

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

103 

104 

105class IssueListView(ListView): 

106 model = Issue 

107 

108 def get_queryset(self): 

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

110 

111 # Prefetch all the necessary things before returning the list 

112 ifas_query = IssueFilterAssociated.objects.all() 

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

114 to_attr='ifas_cached', 

115 queryset=ifas_query), 

116 "ifas_cached__filter", 

117 "ifas_cached__added_by", 

118 "ifas_cached__deleted_by", 

119 "bugs__tracker", 

120 "bugs") 

121 if self.query.orderby is None: 

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

123 return issues 

124 

125 def get_context_data(self): 

126 context = super().get_context_data() 

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

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

129 context['filters_model'] = Issue 

130 context['query'] = self.query 

131 return context 

132 

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

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

135 

136 

137class IssueView(View): 

138 ParsedParams = namedtuple('ParsedParams', 

139 'bugs bugs_form description error filters') 

140 

141 def __parse_params__(self, params): 

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

143 error = False 

144 

145 # Parse the bugs 

146 bugs = [] 

147 bugs_form = [] 

148 for param in params: 

149 if param.startswith("bug_id_"): 

150 # Check the bug_id 

151 bug_id = params[param].strip() 

152 if len(bug_id) == 0: 

153 continue 

154 

155 # Check the tracker 

156 suffix = param[7:] 

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

158 try: 

159 tracker_pk = int(tracker_pk) 

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

161 tracker_valid = True 

162 

163 # Check the bug 

164 try: 

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

166 bugs.append(bug) 

167 bug_valid = True 

168 except Exception: 

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

170 try: 

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

172 bug.poll() 

173 bugs.append(bug) 

174 bug_valid = True 

175 except Exception: 

176 error = True 

177 bug_valid = False 

178 traceback.print_exc() 

179 except Exception: 

180 error = True 

181 tracker_valid = False 

182 bug_valid = False 

183 traceback.print_exc() 

184 

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

186 # use it again to re-draw the page 

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

188 "bug_valid": bug_valid, 

189 "tracker_valid": tracker_valid}) 

190 

191 # Parse the filters 

192 filters = [] 

193 for param in params: 

194 if param.startswith("filter_"): 

195 try: 

196 pk = int(params[param]) 

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

198 except Exception: 

199 error = True 

200 continue 

201 

202 # Get the description 

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

204 

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

206 description=description, error=error, 

207 filters=filters) 

208 

209 def __fetch_from_db__(self, pk): 

210 issue = get_object_or_404(Issue, pk=pk) 

211 

212 bugs = issue.bugs.all() 

213 bugs_form = [] 

214 for bug in bugs: 

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

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

217 

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

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

220 

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

222 description=issue.description, error=False, 

223 filters=filters) 

224 

225 @transaction.atomic 

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

227 if d.error: 

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

229 

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

231 raise ValueError("No filters found") 

232 

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

234 raise ValueError("No bugs found") 

235 

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

237 try: 

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

239 

240 # Update the fields 

241 issue.description = d.description 

242 issue.save() 

243 except Issue.DoesNotExist: 

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

245 description=d.description) 

246 

247 # Add the bugs and filters that were set 

248 issue.set_bugs(d.bugs) 

249 issue.set_filters(d.filters, user) 

250 

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

252 # Pre-allocate a line for the bug 

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

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

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

256 

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

258 if pk is None: 

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

260 issue_title = "New Issue" 

261 else: 

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

263 "pk": pk}) 

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

265 

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

267 for f in d.filters: 

268 if f not in filters: 

269 filters.append(f) 

270 

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

272 # that have an associated first runconfig 

273 tests = set() 

274 for f in d.filters: 

275 tests.update(f.tests.all()) 

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

277 

278 # Render the page 

279 context = { 

280 "issue_id": pk, 

281 "issue_title": issue_title, 

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

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

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

285 "tests": tests, 

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

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

288 "filters": filters, 

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

290 "field_filters": d.filters, 

291 "field_description": d.description, 

292 "save_url": save_url, 

293 "filters_model": UnknownFailure, 

294 } 

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

296 

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

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

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

300 raise PermissionDenied() 

301 

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

303 d = self.__fetch_from_db__(pk) 

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

305 else: 

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

307 raise PermissionDenied() 

308 

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

310 d = self.__parse_params__(params) 

311 

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

313 try: 

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

315 return redirect('CIResults-index') 

316 except ValueError: 

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

318 

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

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

321 

322 action = kwargs['action'] 

323 if action in post_actions: 

324 if request.method != 'POST': 

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

326 

327 # Check permissions 

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

329 raise PermissionDenied() 

330 

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

332 

333 if action == 'archive': 

334 issue.archive(request.user) 

335 else: 

336 getattr(issue, action)() 

337 

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

339 else: 

340 # Action == edit or create 

341 return self.__edit_issue__(request, action, 

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

343 

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

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

346 

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

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

349 

350 

351class IssueFilterView(View): 

352 def __update_stats__(self, stats, result): 

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

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

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

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

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

358 else: 

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

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

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

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

363 

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

365 try: 

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

367 re.compile(regexp) 

368 return True 

369 except re.error as e: 

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

371 return False 

372 

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

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

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

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

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

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

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

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

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

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

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

384 

385 # Assign for purpose of converting to user query 

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

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

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

389 return filter 

390 

391 def __stats__(self, request): 

392 stats = { 

393 "matched": { 

394 "tags": set(), 

395 "machine_tags": set(), 

396 "machines": set(), 

397 "tagless_machines": set(), 

398 "tests": set(), 

399 "statuses": set() 

400 }, 

401 "errors": [ 

402 ], 

403 } 

404 

405 # Read the parameters and perform some input validation 

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

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

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

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

410 

411 filter = self.__parse_filter_from_params__(params) 

412 

413 parser = QueryParser(UnknownFailure, filter.equivalent_user_query) 

414 if parser.error: 

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

416 

417 # Check the filter 

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

419 filter = self.__parse_filter_from_params__(params) 

420 

421 # Check which failures are matched by this filter 

422 matched_failures = ( 

423 parser.objects.select_related("result") 

424 .prefetch_related( 

425 "result__ts_run__runconfig__tags", 

426 "result__ts_run__machine", 

427 "result__ts_run__machine__tags", 

428 "result__test", 

429 "result__test__testsuite", 

430 "result__status", 

431 ) 

432 .filter(result__ts_run__runconfig__temporary=False) 

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

434 ) 

435 

436 for failure in matched_failures: 

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

438 

439 for key in stats['matched']: 

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

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

442 result__ts_run__runconfig__temporary=False 

443 ).count() 

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

445 else: 

446 for key in stats['matched']: 

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

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

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

450 

451 return JsonResponse(stats) 

452 

453 def __convert_to_user_query__(self, request): 

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

455 filter = self.__parse_filter_from_params__(params) 

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

457 

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

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

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

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

462 raise PermissionDenied() 

463 

464 action = kwargs.get('action') 

465 if action == "stats": 

466 return self.__stats__(request) 

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

468 return self.__convert_to_user_query__(request) 

469 

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

471 

472 

473class MassVettingView(PermissionRequiredMixin, View): 

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

475 object_ids = set() 

476 for param in request.POST: 

477 try: 

478 object_ids.add(int(param)) 

479 except ValueError: 

480 continue 

481 

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

483 for obj in objects: 

484 obj.vet() 

485 

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

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

488 ", ".join(objects_str))) 

489 

490 return redirect('CIResults-index') 

491 

492 

493class MachineMassVettingView(MassVettingView): 

494 model = Machine 

495 permission_required = 'CIResults.vet_machine' 

496 

497 

498class TestMassVettingView(MassVettingView): 

499 model = Test 

500 permission_required = 'CIResults.vet_test' 

501 

502 

503class TextStatusMassVettingView(MassVettingView): 

504 model = TextStatus 

505 permission_required = 'CIResults.vet_textstatus' 

506 

507 

508class TestEditView(PermissionRequiredMixin, UpdateView): 

509 model = Test 

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

511 template_name = 'CIResults/test_edit.html' 

512 permission_required = 'CIResults.change_test' 

513 

514 def get_success_url(self): 

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

516 return reverse("CIResults-tests") 

517 

518 

519class MachineEditView(PermissionRequiredMixin, UpdateView): 

520 model = Machine 

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

522 template_name = 'CIResults/machine_edit.html' 

523 permission_required = 'CIResults.change_machine' 

524 

525 def get_success_url(self): 

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

527 return reverse("CIResults-machines") 

528 

529 

530class TestMassRenameView(PermissionRequiredMixin, FormView): 

531 form_class = TestMassRenameForm 

532 template_name = 'CIResults/test_mass_rename.html' 

533 permission_required = 'CIResults.change_test' 

534 

535 def get_success_url(self): 

536 return reverse("CIResults-tests") 

537 

538 def form_valid(self, form): 

539 if not form.is_valid(): 

540 return super().form_valid(form) 

541 

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

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

544 else: 

545 form.do_renaming() 

546 messages.success(self.request, 

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

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

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

550 return super().form_valid(form) 

551 

552 

553class TestRenameView(PermissionRequiredMixin, UpdateView): 

554 model = Test 

555 fields = ['name'] 

556 template_name = 'CIResults/test_rename.html' 

557 permission_required = 'CIResults.change_test' 

558 

559 def form_valid(self, form): 

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

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

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

563 form.cleaned_data['name'])) 

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

565 

566 

567class filterView(DetailView): 

568 model = IssueFilter 

569 template_name = 'CIResults/filter.html' 

570 

571 

572def object_vet(request, **kwargs): 

573 if not request.user.has_perm(kwargs['permission']): 

574 raise PermissionDenied() 

575 

576 klass = kwargs['klass'] 

577 obj = get_object_or_404(klass, pk=kwargs['pk']) 

578 

579 try: 

580 obj.vet() 

581 messages.success(request, "The object {} has been successfully vetted".format(obj)) 

582 except ValueError: 

583 messages.error(request, "The object {} is already vetted".format(obj)) 

584 

585 return HttpResponseRedirect(request.META.get('HTTP_REFERER')) 

586 

587 

588def object_suppress(request, **kwargs): 

589 if not request.user.has_perm(kwargs['permission']): 

590 raise PermissionDenied() 

591 

592 klass = kwargs['klass'] 

593 obj = get_object_or_404(klass, pk=kwargs['pk']) 

594 

595 try: 

596 obj.suppress() 

597 messages.success(request, "The object {} has been successfully suppressed".format(obj)) 

598 except ValueError: 

599 messages.error(request, "The object {} is already suppressed".format(obj)) 

600 

601 return HttpResponseRedirect(request.META.get('HTTP_REFERER')) 

602 

603 

604class SimpleSearchableMixin: 

605 @property 

606 def query(self): 

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

608 

609 # TODO: Add a nice UI for this 

610 def get_paginate_by(self, queryset): 

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

612 

613 def get_context_data(self): 

614 context = super().get_context_data() 

615 context['search_query'] = self.query 

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

617 return context 

618 

619 

620class TestListView(ListView): 

621 model = Test 

622 paginate_by = 100 

623 

624 def get_queryset(self): 

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

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

627 'first_runconfig') 

628 return tests.exclude(first_runconfig=None) 

629 

630 

631class MachineListView(ListView): 

632 model = Machine 

633 paginate_by = 100 

634 

635 def get_queryset(self): 

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

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

638 

639 return machines 

640 

641 

642class UserFiltrableMixin: 

643 # TODO: Add a nice UI for this 

644 def get_paginate_by(self, queryset): 

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

646 

647 def get_context_data(self): 

648 context = super().get_context_data() 

649 context['query_get_param'] = 'query=' + self.query.user_query 

650 context['query'] = self.query 

651 

652 return context 

653 

654 

655class TestResultListView(UserFiltrableMixin, ListView): 

656 model = TestResult 

657 paginate_by = 100 

658 

659 def get_userquery(self): 

660 return None 

661 

662 def get_queryset(self): 

663 if self.get_userquery() is None: 

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

665 else: 

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

667 

668 if not self.query.is_empty: 

669 query = self.query.objects 

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

671 query = query.prefetch_related('test', 

672 'test__testsuite', 

673 'ts_run', 

674 'ts_run__machine', 

675 'ts_run__runconfig', 

676 'ts_run__testsuite', 

677 'status', 

678 'status__testsuite', 

679 'known_failures__matched_ifa__filter', 

680 'known_failures__matched_ifa__issue__bugs__tracker') 

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

682 if self.query.orderby is None: 

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

684 return query 

685 else: 

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

687 

688 def get_context_data(self): 

689 context = super().get_context_data() 

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

691 

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

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

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

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

696 

697 if self.get_userquery() is not None: 

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

699 

700 return context 

701 

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

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

704 

705 

706class KnownFailureListView(UserFiltrableMixin, ListView): 

707 model = KnownFailure 

708 paginate_by = 100 

709 

710 def get_userquery(self): 

711 return None 

712 

713 def get_queryset(self): 

714 if self.get_userquery() is None: 

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

716 else: 

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

718 

719 if not self.query.is_empty: 

720 query = self.query.objects 

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

722 query = query.prefetch_related('result__test', 

723 'result__test__testsuite', 

724 'result__ts_run', 

725 'result__ts_run__machine', 

726 'result__ts_run__runconfig', 

727 'result__ts_run__testsuite', 

728 'result__status', 

729 'result__status__testsuite', 

730 'matched_ifa__filter', 

731 'matched_ifa__issue__bugs__tracker') 

732 if self.query.orderby is None: 

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

734 return query 

735 else: 

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

737 

738 def get_context_data(self): 

739 context = super().get_context_data() 

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

741 

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

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

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

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

746 

747 if self.get_userquery() is not None: 

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

749 

750 return context 

751 

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

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

754 

755 

756class IssueDetailView(KnownFailureListView): 

757 template_name = 'CIResults/issue_detail.html' 

758 

759 @cached_property 

760 def issue(self): 

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

762 

763 def get_userquery(self): 

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

765 

766 def get_context_data(self): 

767 context = super().get_context_data() 

768 context['issue'] = self.issue 

769 return context 

770 

771 

772class IFADetailView(KnownFailureListView): 

773 template_name = 'CIResults/ifa_detail.html' 

774 

775 @cached_property 

776 def ifa(self): 

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

778 

779 def get_userquery(self): 

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

781 

782 def get_context_data(self): 

783 context = super().get_context_data() 

784 context['ifa'] = self.ifa 

785 return context 

786 

787 

788class TestSuiteDetailView(KnownFailureListView): 

789 template_name = 'CIResults/testsuite_detail.html' 

790 

791 @cached_property 

792 def testsuite(self): 

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

794 

795 def get_userquery(self): 

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

797 

798 def get_context_data(self): 

799 context = super().get_context_data() 

800 context['testsuite'] = self.testsuite 

801 return context 

802 

803 

804class TestDetailView(KnownFailureListView): 

805 template_name = 'CIResults/test_detail.html' 

806 

807 @cached_property 

808 def test(self): 

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

810 

811 def get_userquery(self): 

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

813 

814 def get_context_data(self): 

815 context = super().get_context_data() 

816 context['test'] = self.test 

817 return context 

818 

819 

820class TextStatusDetailView(KnownFailureListView): 

821 template_name = 'CIResults/textstatus_detail.html' 

822 

823 @cached_property 

824 def status(self): 

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

826 

827 def get_userquery(self): 

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

829 

830 def get_context_data(self): 

831 context = super().get_context_data() 

832 context['status'] = self.status 

833 return context 

834 

835 

836class MachineDetailView(KnownFailureListView): 

837 template_name = 'CIResults/machine_detail.html' 

838 

839 @cached_property 

840 def machine(self): 

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

842 

843 def get_userquery(self): 

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

845 

846 def get_context_data(self): 

847 context = super().get_context_data() 

848 context['machine'] = self.machine 

849 return context 

850 

851 

852class TestResultDetailView(TestResultListView): 

853 template_name = 'CIResults/testresult_detail.html' 

854 

855 @cached_property 

856 def testresult(self): 

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

858 

859 def get_userquery(self): 

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

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

862 

863 def get_context_data(self): 

864 context = super().get_context_data() 

865 context['testresult'] = self.testresult 

866 return context 

867 

868 

869class RunConfigDetailView(KnownFailureListView): 

870 template_name = 'CIResults/runconfig_detail.html' 

871 

872 @cached_property 

873 def runconfig(self): 

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

875 

876 def get_userquery(self): 

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

878 

879 def get_context_data(self): 

880 context = super().get_context_data() 

881 context['runconfig'] = self.runconfig 

882 return context 

883 

884 

885class RunConfigTagDetailView(KnownFailureListView): 

886 template_name = 'CIResults/runconfig_tag_detail.html' 

887 

888 @cached_property 

889 def tag(self): 

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

891 

892 def get_userquery(self): 

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

894 

895 def get_context_data(self): 

896 context = super().get_context_data() 

897 context['tag'] = self.tag 

898 return context 

899 

900 

901class BuildDetailView(KnownFailureListView): 

902 template_name = 'CIResults/build_detail.html' 

903 

904 @cached_property 

905 def build(self): 

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

907 

908 def get_userquery(self): 

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

910 

911 def get_context_data(self): 

912 context = super().get_context_data() 

913 context['build'] = self.build 

914 return context 

915 

916 

917class ComponentDetailView(KnownFailureListView): 

918 template_name = 'CIResults/component_detail.html' 

919 

920 @cached_property 

921 def component(self): 

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

923 

924 def get_userquery(self): 

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

926 

927 def get_context_data(self): 

928 context = super().get_context_data() 

929 context['component'] = self.component 

930 return context 

931 

932 

933class ResultsCompareView(TemplateView): 

934 template_name = "CIResults/compare_results.html" 

935 

936 def name_to_runconfig(self, name): 

937 if name is None: 

938 return None, False 

939 

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

941 return run, run is None 

942 

943 def urlify(self, markdown): 

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

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

946 

947 def get_context_data(self): 

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

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

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

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

952 

953 context = super().get_context_data() 

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

955 context['from_error'] = from_error 

956 context['to_error'] = to_error 

957 context['no_compress'] = no_compress 

958 context['filters_model'] = TestResult 

959 context['query'] = query 

960 

961 if run_from and run_to: 

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

963 context['diff'] = diff 

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

965 extensions=['nl2br'])) 

966 

967 return context