Coverage for CIResults/views.py: 79%
590 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-23 13:11 +0000
« 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
17from collections import namedtuple
19from .models import BugTracker, Bug, Component, Build, Test, Machine, RunConfigTag, RunConfig, TestSuite, TextStatus
20from .models import TestResult, IssueFilter, IssueFilterAssociated, Issue, KnownFailure, UnknownFailure, MachineTag
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
27from .forms import TestMassRenameForm
29from .filtering import QueryCreator, QueryParser
31import traceback
32import markdown
33import json
34import re
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')
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')
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)
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)
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)
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)
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)
105class IssueListView(ListView):
106 model = Issue
108 def get_queryset(self):
109 self.query = QueryCreator(self.request, self.model).request_to_query()
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
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
133 def post(self, request, *args, **kwargs):
134 return super().get(request, *args, **kwargs)
137class IssueView(View):
138 ParsedParams = namedtuple('ParsedParams',
139 'bugs bugs_form description error filters')
141 def __parse_params__(self, params):
142 # Keep track of all errors, only commit if no error occured
143 error = False
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
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
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()
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})
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
202 # Get the description
203 description = params.get("description", "")
205 return self.ParsedParams(bugs=bugs, bugs_form=bugs_form,
206 description=description, error=error,
207 filters=filters)
209 def __fetch_from_db__(self, pk):
210 issue = get_object_or_404(Issue, pk=pk)
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})
218 filters_assoc = IssueFilterAssociated.objects.filter(deleted_on=None, issue=issue)
219 filters = [e.filter for e in filters_assoc]
221 return self.ParsedParams(bugs=bugs, bugs_form=bugs_form,
222 description=issue.description, error=False,
223 filters=filters)
225 @transaction.atomic
226 def __save__(self, pk, d, user):
227 if d.error:
228 raise ValueError("The data provided contained error")
230 if len(d.filters) == 0:
231 raise ValueError("No filters found")
233 if len(d.bugs) == 0:
234 raise ValueError("No bugs found")
236 # Try to find the issue to edit, if applicable
237 try:
238 issue = Issue.objects.get(id=pk)
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)
247 # Add the bugs and filters that were set
248 issue.set_bugs(d.bugs)
249 issue.set_filters(d.filters, user)
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})
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)
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)
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))
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)
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()
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()
309 # In any other case, just display what we have, or default values
310 d = self.__parse_params__(params)
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)
319 def __route_request__(self, request, kwargs, params):
320 post_actions = ["archive", "restore", "hide", "show"]
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))
327 # Check permissions
328 if not request.user.has_perm('CIResults.{}_issue'.format(action)):
329 raise PermissionDenied()
331 issue = get_object_or_404(Issue, pk=kwargs.get('pk'))
333 if action == 'archive':
334 issue.archive(request.user)
335 else:
336 getattr(issue, action)()
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)
344 def get(self, request, *args, **kwargs):
345 return self.__route_request__(request, kwargs, request.GET)
347 def post(self, request, *args, **kwargs):
348 return self.__route_request__(request, kwargs, request.POST)
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)))
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
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", []))
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
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 }
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'])
411 filter = self.__parse_filter_from_params__(params)
413 parser = QueryParser(UnknownFailure, filter.equivalent_user_query)
414 if parser.error:
415 stats['errors'].append({"field": "user_query", "msg": parser.error})
417 # Check the filter
418 if len(stats['errors']) == 0:
419 filter = self.__parse_filter_from_params__(params)
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 )
436 for failure in matched_failures:
437 self.__update_stats__(stats['matched'], failure.result)
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
451 return JsonResponse(stats)
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()})
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()
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)
470 raise ValidationError("The action '{}' is unsupported".format(action))
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
482 objects = self.model.objects.filter(pk__in=object_ids)
483 for obj in objects:
484 obj.vet()
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)))
490 return redirect('CIResults-index')
493class MachineMassVettingView(MassVettingView):
494 model = Machine
495 permission_required = 'CIResults.vet_machine'
498class TestMassVettingView(MassVettingView):
499 model = Test
500 permission_required = 'CIResults.vet_test'
503class TextStatusMassVettingView(MassVettingView):
504 model = TextStatus
505 permission_required = 'CIResults.vet_textstatus'
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'
514 def get_success_url(self):
515 messages.success(self.request, "The test {} has been edited".format(self.object))
516 return reverse("CIResults-tests")
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'
525 def get_success_url(self):
526 messages.success(self.request, "The machine {} has been edited".format(self.object))
527 return reverse("CIResults-machines")
530class TestMassRenameView(PermissionRequiredMixin, FormView):
531 form_class = TestMassRenameForm
532 template_name = 'CIResults/test_mass_rename.html'
533 permission_required = 'CIResults.change_test'
535 def get_success_url(self):
536 return reverse("CIResults-tests")
538 def form_valid(self, form):
539 if not form.is_valid():
540 return super().form_valid(form)
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)
553class TestRenameView(PermissionRequiredMixin, UpdateView):
554 model = Test
555 fields = ['name']
556 template_name = 'CIResults/test_rename.html'
557 permission_required = 'CIResults.change_test'
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"))
567class filterView(DetailView):
568 model = IssueFilter
569 template_name = 'CIResults/filter.html'
572def object_vet(request, **kwargs):
573 if not request.user.has_perm(kwargs['permission']):
574 raise PermissionDenied()
576 klass = kwargs['klass']
577 obj = get_object_or_404(klass, pk=kwargs['pk'])
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))
585 return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
588def object_suppress(request, **kwargs):
589 if not request.user.has_perm(kwargs['permission']):
590 raise PermissionDenied()
592 klass = kwargs['klass']
593 obj = get_object_or_404(klass, pk=kwargs['pk'])
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))
601 return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
604class SimpleSearchableMixin:
605 @property
606 def query(self):
607 return self.request.GET.get('q', '')
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)
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
620class TestListView(ListView):
621 model = Test
622 paginate_by = 100
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)
631class MachineListView(ListView):
632 model = Machine
633 paginate_by = 100
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')
639 return machines
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)
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
652 return context
655class TestResultListView(UserFiltrableMixin, ListView):
656 model = TestResult
657 paginate_by = 100
659 def get_userquery(self):
660 return None
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())
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')
688 def get_context_data(self):
689 context = super().get_context_data()
690 context['results'] = context.pop('object_list')
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()
697 if self.get_userquery() is not None:
698 context['filter_url'] = reverse('CIResults-results')
700 return context
702 def post(self, request, *args, **kwargs):
703 return super().get(request, *args, **kwargs)
706class KnownFailureListView(UserFiltrableMixin, ListView):
707 model = KnownFailure
708 paginate_by = 100
710 def get_userquery(self):
711 return None
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())
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')
738 def get_context_data(self):
739 context = super().get_context_data()
740 context['failures'] = context.pop('object_list')
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()
747 if self.get_userquery() is not None:
748 context['filter_url'] = reverse('CIResults-knownfailures')
750 return context
752 def post(self, request, *args, **kwargs):
753 return super().get(request, *args, **kwargs)
756class IssueDetailView(KnownFailureListView):
757 template_name = 'CIResults/issue_detail.html'
759 @cached_property
760 def issue(self):
761 return get_object_or_404(Issue, pk=self.kwargs['pk'])
763 def get_userquery(self):
764 return {'query': "issue_id={}".format(self.issue.id)}
766 def get_context_data(self):
767 context = super().get_context_data()
768 context['issue'] = self.issue
769 return context
772class IFADetailView(KnownFailureListView):
773 template_name = 'CIResults/ifa_detail.html'
775 @cached_property
776 def ifa(self):
777 return get_object_or_404(IssueFilterAssociated, pk=self.kwargs['pk'])
779 def get_userquery(self):
780 return {'query': "ifa_id={}".format(self.ifa.id)}
782 def get_context_data(self):
783 context = super().get_context_data()
784 context['ifa'] = self.ifa
785 return context
788class TestSuiteDetailView(KnownFailureListView):
789 template_name = 'CIResults/testsuite_detail.html'
791 @cached_property
792 def testsuite(self):
793 return get_object_or_404(TestSuite, pk=self.kwargs['pk'])
795 def get_userquery(self):
796 return {'query': "testsuite_name='{}'".format(self.testsuite.name)}
798 def get_context_data(self):
799 context = super().get_context_data()
800 context['testsuite'] = self.testsuite
801 return context
804class TestDetailView(KnownFailureListView):
805 template_name = 'CIResults/test_detail.html'
807 @cached_property
808 def test(self):
809 return get_object_or_404(Test, pk=self.kwargs['pk'])
811 def get_userquery(self):
812 return {'query': "test_name='{}'".format(self.test.name)}
814 def get_context_data(self):
815 context = super().get_context_data()
816 context['test'] = self.test
817 return context
820class TextStatusDetailView(KnownFailureListView):
821 template_name = 'CIResults/textstatus_detail.html'
823 @cached_property
824 def status(self):
825 return get_object_or_404(TextStatus, pk=self.kwargs['pk'])
827 def get_userquery(self):
828 return {'query': "status_name='{}'".format(self.status.name)}
830 def get_context_data(self):
831 context = super().get_context_data()
832 context['status'] = self.status
833 return context
836class MachineDetailView(KnownFailureListView):
837 template_name = 'CIResults/machine_detail.html'
839 @cached_property
840 def machine(self):
841 return get_object_or_404(Machine, pk=self.kwargs['pk'])
843 def get_userquery(self):
844 return {'query': "machine_name='{}'".format(self.machine.name)}
846 def get_context_data(self):
847 context = super().get_context_data()
848 context['machine'] = self.machine
849 return context
852class TestResultDetailView(TestResultListView):
853 template_name = 'CIResults/testresult_detail.html'
855 @cached_property
856 def testresult(self):
857 return get_object_or_404(TestResult, pk=self.kwargs['pk'])
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)}
863 def get_context_data(self):
864 context = super().get_context_data()
865 context['testresult'] = self.testresult
866 return context
869class RunConfigDetailView(KnownFailureListView):
870 template_name = 'CIResults/runconfig_detail.html'
872 @cached_property
873 def runconfig(self):
874 return get_object_or_404(RunConfig, pk=self.kwargs['pk'])
876 def get_userquery(self):
877 return {'query': "runconfig_name='{}'".format(self.runconfig.name)}
879 def get_context_data(self):
880 context = super().get_context_data()
881 context['runconfig'] = self.runconfig
882 return context
885class RunConfigTagDetailView(KnownFailureListView):
886 template_name = 'CIResults/runconfig_tag_detail.html'
888 @cached_property
889 def tag(self):
890 return get_object_or_404(RunConfigTag, pk=self.kwargs['pk'])
892 def get_userquery(self):
893 return {'query': "runconfig_tag='{}'".format(self.tag.name)}
895 def get_context_data(self):
896 context = super().get_context_data()
897 context['tag'] = self.tag
898 return context
901class BuildDetailView(KnownFailureListView):
902 template_name = 'CIResults/build_detail.html'
904 @cached_property
905 def build(self):
906 return get_object_or_404(Build, pk=self.kwargs['pk'])
908 def get_userquery(self):
909 return {'query': "build_name='{}'".format(self.build.name)}
911 def get_context_data(self):
912 context = super().get_context_data()
913 context['build'] = self.build
914 return context
917class ComponentDetailView(KnownFailureListView):
918 template_name = 'CIResults/component_detail.html'
920 @cached_property
921 def component(self):
922 return get_object_or_404(Component, pk=self.kwargs['pk'])
924 def get_userquery(self):
925 return {'query': "component_name='{}'".format(self.component.name)}
927 def get_context_data(self):
928 context = super().get_context_data()
929 context['component'] = self.component
930 return context
933class ResultsCompareView(TemplateView):
934 template_name = "CIResults/compare_results.html"
936 def name_to_runconfig(self, name):
937 if name is None:
938 return None, False
940 run = RunConfig.objects.filter(name=name).first()
941 return run, run is None
943 def urlify(self, markdown):
944 regex = re.compile(r'(https?:\/\/[^\s,;]+)')
945 return regex.sub(r'<\1>', markdown)
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()
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
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']))
967 return context