Coverage for CIResults / views.py: 78%
587 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 09:21 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 09:21 +0000
1from django.contrib import messages
2from django.contrib.auth.mixins import PermissionRequiredMixin
3from django.shortcuts import render, redirect, get_object_or_404
4from django.core.exceptions import ValidationError, PermissionDenied
5from django.utils.safestring import mark_safe
6from django.views import View
7from django.views.generic import DetailView, ListView
8from django.views.generic.base import TemplateView
9from django.views.generic.edit import UpdateView, FormView
10from django.http import JsonResponse, HttpResponseRedirect
11from django.db import transaction
12from django.db.models import Prefetch
13from django.urls import reverse
14from django.utils.functional import cached_property
15from django.core.paginator import Paginator
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 = SafePaginator(object_query, page_size)
68 return paginator.page(page)
70 query_get_param = request.GET.copy()
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)
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)
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']])
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)
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)
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)
126class IssueListView(ListView):
127 model = Issue
129 def get_queryset(self):
130 self.query = QueryCreator(self.request, self.model).request_to_query()
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
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
154 def post(self, request, *args, **kwargs):
155 return super().get(request, *args, **kwargs)
158class IssueView(View):
159 ParsedParams = namedtuple('ParsedParams',
160 'bugs bugs_form description error filters')
162 def __parse_params__(self, params):
163 # Keep track of all errors, only commit if no error occured
164 error = False
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
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
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()
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})
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
223 # Get the description
224 description = params.get("description", "")
226 return self.ParsedParams(bugs=bugs, bugs_form=bugs_form,
227 description=description, error=error,
228 filters=filters)
230 def __fetch_from_db__(self, pk):
231 issue = get_object_or_404(Issue, pk=pk)
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})
239 filters_assoc = IssueFilterAssociated.objects.filter(deleted_on=None, issue=issue)
240 filters = [e.filter for e in filters_assoc]
242 return self.ParsedParams(bugs=bugs, bugs_form=bugs_form,
243 description=issue.description, error=False,
244 filters=filters)
246 @transaction.atomic
247 def __save__(self, pk, d, user):
248 if d.error:
249 raise ValueError("The data provided contained error")
251 if len(d.filters) == 0:
252 raise ValueError("No filters found")
254 if len(d.bugs) == 0:
255 raise ValueError("No bugs found")
257 # Try to find the issue to edit, if applicable
258 try:
259 issue = Issue.objects.get(id=pk)
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)
268 # Add the bugs and filters that were set
269 issue.set_filters(d.filters, user)
270 issue.set_bugs(d.bugs)
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})
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)
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)
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))
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)
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()
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()
331 # In any other case, just display what we have, or default values
332 d = self.__parse_params__(params)
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)
341 def __route_request__(self, request, kwargs, params):
342 post_actions = ["archive", "restore", "hide", "show"]
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))
349 # Check permissions
350 if not request.user.has_perm('CIResults.{}_issue'.format(action)):
351 raise PermissionDenied()
353 issue = get_object_or_404(Issue, pk=kwargs.get('pk'))
355 if action == 'archive':
356 issue.archive(request.user)
357 else:
358 getattr(issue, action)()
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)
366 def get(self, request, *args, **kwargs):
367 return self.__route_request__(request, kwargs, request.GET)
369 def post(self, request, *args, **kwargs):
370 return self.__route_request__(request, kwargs, request.POST)
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)))
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
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", []))
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
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 }
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'])
433 filter = self.__parse_filter_from_params__(params)
435 parser = QueryParser(UnknownFailure, filter.equivalent_user_query)
436 if parser.error:
437 stats['errors'].append({"field": "user_query", "msg": parser.error})
439 # Check the filter
440 if len(stats['errors']) == 0:
441 filter = self.__parse_filter_from_params__(params)
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 )
458 for failure in matched_failures:
459 self.__update_stats__(stats['matched'], failure.result)
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
473 return JsonResponse(stats)
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()})
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()
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)
492 raise ValidationError("The action '{}' is unsupported".format(action))
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
504 objects = self.model.objects.filter(pk__in=object_ids)
505 for obj in objects:
506 obj.vet()
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)))
512 return redirect('CIResults-index')
515class MachineMassVettingView(MassVettingView):
516 model = Machine
517 permission_required = 'CIResults.vet_machine'
520class TestMassVettingView(MassVettingView):
521 model = Test
522 permission_required = 'CIResults.vet_test'
525class TextStatusMassVettingView(MassVettingView):
526 model = TextStatus
527 permission_required = 'CIResults.vet_textstatus'
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'
536 def get_success_url(self):
537 messages.success(self.request, "The test {} has been edited".format(self.object))
538 return reverse("CIResults-tests")
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'
547 def get_success_url(self):
548 messages.success(self.request, "The machine {} has been edited".format(self.object))
549 return reverse("CIResults-machines")
552class TestMassRenameView(PermissionRequiredMixin, FormView):
553 form_class = TestMassRenameForm
554 template_name = 'CIResults/test_mass_rename.html'
555 permission_required = 'CIResults.change_test'
557 def get_success_url(self):
558 return reverse("CIResults-tests")
560 def form_valid(self, form):
561 if not form.is_valid():
562 return super().form_valid(form)
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)
575class TestRenameView(PermissionRequiredMixin, UpdateView):
576 model = Test
577 fields = ['name']
578 template_name = 'CIResults/test_rename.html'
579 permission_required = 'CIResults.change_test'
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"))
589class filterView(DetailView):
590 model = IssueFilter
591 template_name = 'CIResults/filter.html'
594class SimpleSearchableMixin:
595 @property
596 def query(self):
597 return self.request.GET.get('q', '')
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)
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
610class TestListView(ListView):
611 model = Test
612 paginator_class = SafePaginator
613 paginate_by = 100
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)
621 def get_context_data(self):
622 context = super().get_context_data()
623 context['query_get_param'] = self.request.GET.copy()
624 return context
627class MachineListView(ListView):
628 model = Machine
629 paginator_class = SafePaginator
630 paginate_by = 100
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')
636 return machines
638 def get_context_data(self):
639 context = super().get_context_data()
640 context['query_get_param'] = self.request.GET.copy()
641 return context
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)
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
654 return context
657class TestResultListView(UserFiltrableMixin, ListView):
658 model = TestResult
659 paginator_class = SafePaginator
660 paginate_by = 100
662 def get_userquery(self):
663 return None
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())
671 if not self.query.is_empty:
672 query = self.query.objects
673 query = query.filter(ts_run__runconfig__temporary=False) # Do not show results from temp runs
674 query = query.prefetch_related('test',
675 'test__testsuite',
676 'ts_run',
677 'ts_run__machine',
678 'ts_run__machine__tags',
679 'ts_run__runconfig',
680 'ts_run__testsuite',
681 'status',
682 'status__testsuite',
683 'known_failures__matched_ifa__filter',
684 'known_failures__matched_ifa__issue__bugs__tracker')
685 query = query.defer('stdout', 'stderr', 'dmesg')
686 if self.query.orderby is None:
687 query = query.order_by('-ts_run__runconfig__added_on')
688 return query
689 else:
690 return self.model.objects.none().order_by('id')
692 def get_context_data(self):
693 context = super().get_context_data()
694 context['results'] = context.pop('object_list')
696 context["statuses_chart"] = metrics_testresult_statuses_stats(context['results']).stats()
697 context["machines_chart"] = metrics_testresult_machines_stats(context['results']).stats()
698 context["tests_chart"] = metrics_testresult_tests_stats(context['results']).stats()
699 context["issues_chart"] = metrics_testresult_issues_stats(context['results']).stats()
701 if self.get_userquery() is not None:
702 context['filter_url'] = reverse('CIResults-results')
704 return context
706 def post(self, request, *args, **kwargs):
707 return super().get(request, *args, **kwargs)
710class KnownFailureListView(UserFiltrableMixin, ListView):
711 model = KnownFailure
712 paginator_class = SafePaginator
713 paginate_by = 100
715 def get_userquery(self):
716 return None
718 def get_queryset(self):
719 if self.get_userquery() is None:
720 self.query = QueryCreator(self.request, self.model).request_to_query()
721 else:
722 self.query = self.model.from_user_filters(**self.get_userquery())
724 if not self.query.is_empty:
725 query = self.query.objects
726 query = query.filter(result__ts_run__runconfig__temporary=False) # Do not show results from temp runs
727 query = query.prefetch_related('result__test',
728 'result__test__testsuite',
729 'result__ts_run',
730 'result__ts_run__machine',
731 'result__ts_run__runconfig',
732 'result__ts_run__testsuite',
733 'result__status',
734 'result__status__testsuite',
735 'matched_ifa__filter',
736 'matched_ifa__issue__bugs__tracker')
737 if self.query.orderby is None:
738 query = query.order_by('-result__ts_run__runconfig__added_on')
739 return query
740 else:
741 return self.model.objects.none().order_by('id')
743 def get_context_data(self):
744 context = super().get_context_data()
745 context['failures'] = context.pop('object_list')
747 context["statuses_chart"] = metrics_knownfailure_statuses_stats(context['failures']).stats()
748 context["machines_chart"] = metrics_knownfailure_machines_stats(context['failures']).stats()
749 context["tests_chart"] = metrics_knownfailure_tests_stats(context['failures']).stats()
750 context["issues_chart"] = metrics_knownfailure_issues_stats(context['failures']).stats()
752 if self.get_userquery() is not None:
753 context['filter_url'] = reverse('CIResults-knownfailures')
755 return context
757 def post(self, request, *args, **kwargs):
758 return super().get(request, *args, **kwargs)
761class IssueDetailView(KnownFailureListView):
762 template_name = 'CIResults/issue_detail.html'
764 @cached_property
765 def issue(self):
766 return get_object_or_404(Issue, pk=self.kwargs['pk'])
768 def get_userquery(self):
769 return {'query': "issue_id={}".format(self.issue.id)}
771 def get_context_data(self):
772 context = super().get_context_data()
773 context['issue'] = self.issue
774 return context
777class IFADetailView(KnownFailureListView):
778 template_name = 'CIResults/ifa_detail.html'
780 @cached_property
781 def ifa(self):
782 return get_object_or_404(IssueFilterAssociated, pk=self.kwargs['pk'])
784 def get_userquery(self):
785 return {'query': "ifa_id={}".format(self.ifa.id)}
787 def get_context_data(self):
788 context = super().get_context_data()
789 context['ifa'] = self.ifa
790 return context
793class TestSuiteDetailView(KnownFailureListView):
794 template_name = 'CIResults/testsuite_detail.html'
796 @cached_property
797 def testsuite(self):
798 return get_object_or_404(TestSuite, pk=self.kwargs['pk'])
800 def get_userquery(self):
801 return {'query': "testsuite_name='{}'".format(self.testsuite.name)}
803 def get_context_data(self):
804 context = super().get_context_data()
805 context['testsuite'] = self.testsuite
806 return context
809class TestDetailView(KnownFailureListView):
810 template_name = 'CIResults/test_detail.html'
812 @cached_property
813 def test(self):
814 return get_object_or_404(Test, pk=self.kwargs['pk'])
816 def get_userquery(self):
817 return {'query': "test_name='{}'".format(self.test.name)}
819 def get_context_data(self):
820 context = super().get_context_data()
821 context['test'] = self.test
822 return context
825class TextStatusDetailView(KnownFailureListView):
826 template_name = 'CIResults/textstatus_detail.html'
828 @cached_property
829 def status(self):
830 return get_object_or_404(TextStatus, pk=self.kwargs['pk'])
832 def get_userquery(self):
833 return {'query': "status_name='{}'".format(self.status.name)}
835 def get_context_data(self):
836 context = super().get_context_data()
837 context['status'] = self.status
838 return context
841class MachineDetailView(KnownFailureListView):
842 template_name = 'CIResults/machine_detail.html'
844 @cached_property
845 def machine(self):
846 return get_object_or_404(Machine, pk=self.kwargs['pk'])
848 def get_userquery(self):
849 return {'query': "machine_name='{}'".format(self.machine.name)}
851 def get_context_data(self):
852 context = super().get_context_data()
853 context['machine'] = self.machine
854 return context
857class TestResultDetailView(TestResultListView):
858 template_name = 'CIResults/testresult_detail.html'
860 @cached_property
861 def testresult(self):
862 return get_object_or_404(TestResult, pk=self.kwargs['pk'])
864 def get_userquery(self):
865 return {'query': "test_name='{}' AND machine_name='{}'".format(self.testresult.test.name,
866 self.testresult.ts_run.machine.name)}
868 def get_context_data(self):
869 context = super().get_context_data()
870 context['testresult'] = self.testresult
871 return context
874class RunConfigDetailView(KnownFailureListView):
875 template_name = 'CIResults/runconfig_detail.html'
877 @cached_property
878 def runconfig(self):
879 return get_object_or_404(RunConfig, pk=self.kwargs['pk'])
881 def get_userquery(self):
882 return {'query': "runconfig_name='{}'".format(self.runconfig.name)}
884 def get_context_data(self):
885 context = super().get_context_data()
886 context['runconfig'] = self.runconfig
887 return context
890class RunConfigTagDetailView(KnownFailureListView):
891 template_name = 'CIResults/runconfig_tag_detail.html'
893 @cached_property
894 def tag(self):
895 return get_object_or_404(RunConfigTag, pk=self.kwargs['pk'])
897 def get_userquery(self):
898 return {'query': "runconfig_tag='{}'".format(self.tag.name)}
900 def get_context_data(self):
901 context = super().get_context_data()
902 context['tag'] = self.tag
903 return context
906class BuildDetailView(KnownFailureListView):
907 template_name = 'CIResults/build_detail.html'
909 @cached_property
910 def build(self):
911 return get_object_or_404(Build, pk=self.kwargs['pk'])
913 def get_userquery(self):
914 return {'query': "build_name='{}'".format(self.build.name)}
916 def get_context_data(self):
917 context = super().get_context_data()
918 context['build'] = self.build
919 return context
922class ComponentDetailView(KnownFailureListView):
923 template_name = 'CIResults/component_detail.html'
925 @cached_property
926 def component(self):
927 return get_object_or_404(Component, pk=self.kwargs['pk'])
929 def get_userquery(self):
930 return {'query': "component_name='{}'".format(self.component.name)}
932 def get_context_data(self):
933 context = super().get_context_data()
934 context['component'] = self.component
935 return context
938class ResultsCompareView(TemplateView):
939 template_name = "CIResults/compare_results.html"
941 def name_to_runconfig(self, name):
942 if name is None:
943 return None, False
945 run = RunConfig.objects.filter(name=name).first()
946 return run, run is None
948 def urlify(self, markdown):
949 regex = re.compile(r'(https?:\/\/[^\s,;]+)')
950 return regex.sub(r'<\1>', markdown)
952 def get_context_data(self):
953 run_from, from_error = self.name_to_runconfig(self.request.GET.get('from'))
954 run_to, to_error = self.name_to_runconfig(self.request.GET.get('to'))
955 no_compress = self.request.GET.get('nocompress') is not None
956 query = QueryCreator(self.request, TestResult).request_to_query()
958 context = super().get_context_data()
959 context['runconfigs'] = RunConfig.objects.filter(temporary=False).order_by('-added_on')[0:50]
960 context['from_error'] = from_error
961 context['to_error'] = to_error
962 context['no_compress'] = no_compress
963 context['filters_model'] = TestResult
964 context['query'] = query
966 if run_from and run_to:
967 diff = run_from.compare(run_to, no_compress=no_compress, query=query)
968 context['diff'] = diff
969 context['html_result'] = mark_safe(markdown.markdown(self.urlify(diff.text),
970 extensions=['nl2br']))
972 return context