diff --git a/bitbake/lib/toaster/bldcontrol/models.py b/bitbake/lib/toaster/bldcontrol/models.py index 2386d2345a..dc4afca2f7 100644 --- a/bitbake/lib/toaster/bldcontrol/models.py +++ b/bitbake/lib/toaster/bldcontrol/models.py @@ -113,6 +113,15 @@ class BuildRequest(models.Model): created = models.DateTimeField(auto_now_add = True) updated = models.DateTimeField(auto_now = True) + def get_duration(self): + return (self.updated - self.created).total_seconds() + + def get_sorted_target_list(self): + tgts = self.brtarget_set.order_by( 'target' ); + return( tgts ); + + def get_machine(self): + return self.brvariable_set.get(name="MACHINE").value # These tables specify the settings for running an actual build. # They MUST be kept in sync with the tables in orm.models.Project* diff --git a/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html b/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html new file mode 100644 index 0000000000..2a4571f42e --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html @@ -0,0 +1,67 @@ +{% extends "baseprojectpage.html" %} + +{% load static %} +{% load projecttags %} +{% load humanize %} + +{% block localbreadcrumb %} +
  • {{buildrequest.get_sorted_target_list.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} {{buildrequest.get_machine}} ({{buildrequest.updated|date:"d/m/y H:i"}})
  • +{% endblock %} + +{% block projectinfomain %} + + +
    + + + +
    + +
    +

    + Failed + on {{ buildrequest.updated|date:'d/m/y H:i' }} + with + + + + {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} + + Build time: {{buildrequest.get_duration|sectohms}} +

    +
    + +
    +
    + +
    +
    +
    + {% for error in buildrequest.brerror_set.all %} +
    + ERROR:
    {{error.errmsg}}
    +
    + {% endfor %} +
    +
    +
    + +
    +
    +
    +
    + + +{%endblock%} diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html index 5944dc4747..183be760ae 100644 --- a/bitbake/lib/toaster/toastergui/templates/managed_builds.html +++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html @@ -35,10 +35,10 @@ - {% else %} + {% else %} {# We have builds to display #} {% include "basetable_top_buildprojects.html" %} - {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #} + {% for buildrequest in objects %}{% if buildrequest.build %} {% with build=buildrequest.build %} {# if we have a build, just display it #} {%if build.outcome == build.SUCCEEDED%}{%elif build.outcome == build.FAILED%}{%else%}{%endif%} {% for t in build.target_set.all %} {{t.target}}
    {% endfor %} @@ -61,7 +61,7 @@ {% if build.errors_no %} {{build.errors_no}} error{{build.errors_no|pluralize}} - {% if MANAGED and build.project %} + {% if MANAGED and build.project and build.buildartifact_set.count %} @@ -96,21 +96,21 @@ {% if buildrequest.state == buildrequest.REQ_FAILED %}{%else%}FIXME_build_request_state{%endif%} - 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} + 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} - {{br.machine}} + {{buildrequest.machine}} - {{br.created|date:"d/m/y H:i"}} + {{buildrequest.created|date:"d/m/y H:i"}} - {{br.updated|date:"d/m/y H:i"}} + {{buildrequest.updated|date:"d/m/y H:i"}} - {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}} + {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} @@ -120,7 +120,7 @@ {# we have no output here #} - {{br.project.name}} + {{buildrequest.project.name}} {%endif%} diff --git a/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html index da5a3f7f74..d2ffdcdc3d 100644 --- a/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html +++ b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html @@ -26,7 +26,7 @@ {% endif %} 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} - + {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} {% endif %} @@ -71,23 +71,41 @@ {% else %} {# we use the project's page recent build design #} -
    + + + +
    + {{buildrequest.project.name}}
    - {% if buildrequest.state == buildrequest.REQ_FAILED %} -
    - 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} + -
    +
    + {% if buildrequest.updated|format_build_date %} + {{ buildrequest.updated|date:'d/m/y H:i' }} + {% else %} + {{ buildrequest.updated|date:'H:i' }} + {% endif %}
    -
    - {% for e in buildrequest.brerror_set.all|slice:":3" %} -
    -
    {{e.errmsg|whitespace_slice:":150"}}
    -
    - {% endfor %} +
    + {% if buildrequest.brerror_set.all.count %} + {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} + {% endif %}
    +
    {# there are no warnings for buildrequests #} +
    +
    + + Build time: {{ buildrequest.get_duration|sectohms }} + + + +
    + {% elif buildrequest.state == buildrequest.REQ_QUEUED %} diff --git a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html index 8f9172c6d5..2a8bd58f34 100644 --- a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html +++ b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html @@ -13,11 +13,11 @@ No builds found {% else %} - {% if request.GET.filter or request.GET.search %} - {{objects.paginator.count}} builds found - {% else %} + {% if request.GET.filter or request.GET.search %} + {{objects.paginator.count}} builds found + {% else %} Project builds ({{objects.paginator.count}}) - {% endif %} + {% endif %} {% endif %} @@ -89,23 +89,23 @@ - {% if buildrequest.state == buildrequest.REQ_FAILED %}{%else%}FIXME_build_request_state{%endif%} + {% if br.state == br.REQ_FAILED %}{%else%}FIXME_build_request_state{%endif%} - 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} + 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} - {{br.machine}} + {{br.machine}} - {{br.created|date:"d/m/y H:i"}} + {{br.created|date:"d/m/y H:i"}} - {{br.updated|date:"d/m/y H:i"}} + {{br.updated|date:"d/m/y H:i"}} - {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}} + {{br.brerror_set.all.count}} error{{br.brerror_set.all.count|pluralize}} diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index 8c3b5a85fd..1c83090f58 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py @@ -97,6 +97,8 @@ urlpatterns = patterns('toastergui.views', url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'), + # dashboard for failed build requests + url(r'^project/(?P\d+)/buildrequest/(?P\d+)$', 'buildrequestdetails', name='buildrequestdetails'), # default redirection url(r'^$', RedirectView.as_view( url= 'landing')), diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index e718ced570..6ccbf5452d 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py @@ -22,7 +22,7 @@ import operator,re import HTMLParser -from django.db.models import Q, Sum +from django.db.models import Q, Sum, Count from django.db import IntegrityError from django.shortcuts import render, redirect from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable @@ -117,7 +117,8 @@ def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs): return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs) FIELD_SEPARATOR = ":" -VALUE_SEPARATOR = "!" +AND_VALUE_SEPARATOR = "!" +OR_VALUE_SEPARATOR = "|" DESCENDING = "-" def __get_q_for_val(name, value): @@ -126,20 +127,31 @@ def __get_q_for_val(name, value): if "AND" in value: return reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ])) if value.startswith("NOT"): - kwargs = { name : value.strip("NOT") } + value = value[3:] + if value == 'None': + value = None + kwargs = { name : value } return ~Q(**kwargs) else: + if value == 'None': + value = None kwargs = { name : value } return Q(**kwargs) def _get_filtering_query(filter_string): search_terms = filter_string.split(FIELD_SEPARATOR) - keys = search_terms[0].split(VALUE_SEPARATOR) - values = search_terms[1].split(VALUE_SEPARATOR) + and_keys = search_terms[0].split(AND_VALUE_SEPARATOR) + and_values = search_terms[1].split(AND_VALUE_SEPARATOR) - querydict = dict(zip(keys, values)) - return reduce(operator.and_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict])) + and_query = [] + for kv in zip(and_keys, and_values): + or_keys = kv[0].split(OR_VALUE_SEPARATOR) + or_values = kv[1].split(OR_VALUE_SEPARATOR) + querydict = dict(zip(or_keys, or_values)) + and_query.append(reduce(operator.or_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict]))) + + return reduce(operator.and_, [k for k in and_query]) def _get_toggle_order(request, orderkey, reverse = False): if reverse: @@ -169,13 +181,13 @@ def _validate_input(input, model): return None, invalid # Check we have an equal number of terms both sides of the colon - if len(input_list[0].split(VALUE_SEPARATOR)) != len(input_list[1].split(VALUE_SEPARATOR)): + if len(input_list[0].split(AND_VALUE_SEPARATOR)) != len(input_list[1].split(AND_VALUE_SEPARATOR)): invalid = "Not all arg names got values" return None, invalid + str(input_list) # Check we are looking for a valid field valid_fields = model._meta.get_all_field_names() - for field in input_list[0].split(VALUE_SEPARATOR): + for field in input_list[0].split(AND_VALUE_SEPARATOR): if not reduce(lambda x, y: x or y, map(lambda x: field.startswith(x), [ x for x in valid_fields ])): return None, (field, [ x for x in valid_fields ]) @@ -216,6 +228,7 @@ def _search_tuple(request, model): def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''): if filter_string: filter_query = _get_filtering_query(filter_string) +# raise Exception(filter_query) queryset = queryset.filter(filter_query) else: queryset = queryset.all() @@ -1780,12 +1793,13 @@ if toastermain.settings.MANAGED: # for that object type. copypasta for all needed table searches (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest) # we don't display in-progress or deleted builds - queryset_all = buildrequests - queryset_with_search = _get_queryset(BuildRequest, queryset_all, None, search_term, ordering_string, '-updated') - queryset = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') + queryset_all = buildrequests.exclude(state = BuildRequest.REQ_DELETED) + queryset_all = queryset_all.annotate(Count('brerror')) + queryset_with_search = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') + # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display - build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1)) + build_info = _build_page_range(Paginator(queryset_with_search, pagesize), request.GET.get('page', 1)) # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) # most recent build is like projects' most recent builds, but across all projects @@ -1842,8 +1856,8 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'outcome', 'label': 'Show:', 'options' : [ - ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_with_search.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression - ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_with_search.filter(state=str(BuildRequest.REQ_FAILED)).count()), + ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_all.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression + ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_all.filter(state=str(BuildRequest.REQ_FAILED)).count()), ] } }, @@ -1865,9 +1879,9 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'created', 'label': 'Show:', 'options' : [ - ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=timezone.now()).count()), - ("Yesterday's builds", 'created__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(hours=24))).count()), - ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), + ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(created__gte=timezone.now()).count()), + ("Yesterday's builds", 'created__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(hours=24))).count()), + ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), ] } }, @@ -1879,9 +1893,9 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'updated', 'label': 'Show:', 'options' : [ - ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=timezone.now()).count()), - ("Yesterday's builds", 'updated__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(hours=24))).count()), - ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), + ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=timezone.now()).count()), + ("Yesterday's builds", 'updated__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(hours=24))).count()), + ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), ] } }, @@ -1890,8 +1904,10 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'failed_tasks', 'label': 'Show:', 'options' : [ - ('Build with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()), - ('Build without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()), + ('Builds with failed tasks', 'build__task_build__outcome:%d' % Task.OUTCOME_FAILED, + queryset_all.filter(build__task_build__outcome=Task.OUTCOME_FAILED).count()), + ('Builds without failed tasks', 'build__task_build__outcome:%d' % Task.OUTCOME_FAILED, + queryset_all.filter(~Q(build__task_build__outcome=Task.OUTCOME_FAILED)).count()), ] } }, @@ -1903,8 +1919,10 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'errors_no', 'label': 'Show:', 'options' : [ - ('Build with errors', 'build__errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()), - ('Build without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()), + ('Builds with errors', 'build|build__errors_no__gt:None|0', + queryset_all.filter(Q(build=None) | Q(build__errors_no__gt=0)).count()), + ('Builds without errors', 'build__errors_no:0', + queryset_all.filter(build__errors_no=0).count()), ] } }, @@ -1916,8 +1934,8 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'build__warnings_no', 'label': 'Show:', 'options' : [ - ('Build with warnings','build__warnings_no__gte:1', queryset_with_search.filter(build__warnings_no__gte=1).count()), - ('Build without warnings','build__warnings_no:0', queryset_with_search.filter(build__warnings_no=0).count()), + ('Builds with warnings','build__warnings_no__gte:1', queryset_all.filter(build__warnings_no__gte=1).count()), + ('Builds without warnings','build__warnings_no:0', queryset_all.filter(build__warnings_no=0).count()), ] } }, @@ -2016,7 +2034,7 @@ if toastermain.settings.MANAGED: context = { "project" : prj, - "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS), + "completedbuilds": BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED), "prj" : {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}}, #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), "builds" : _project_recent_build_list(prj), @@ -2061,7 +2079,7 @@ if toastermain.settings.MANAGED: try: if request.method != "POST": raise BadParameterException("invalid method") - request.session['project_id'] = pid + request.session['project_id'] = pid prj = Project.objects.get(id = pid) @@ -2167,11 +2185,11 @@ if toastermain.settings.MANAGED: try: prj = None if request.GET.has_key('project_id'): - prj = Project.objects.get(pk = request.GET['project_id']) + prj = Project.objects.get(pk = request.GET['project_id']) elif 'project_id' in request.session: prj = Project.objects.get(pk = request.session['project_id']) - else: - raise Exception("No valid project selected") + else: + raise Exception("No valid project selected") def _lv_to_dict(x): @@ -2819,10 +2837,10 @@ if toastermain.settings.MANAGED: vars_blacklist = { 'DL_DR','PARALLEL_MAKE','BB_NUMBER_THREADS','SSTATE_DIR', - 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', - 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR', - 'all_proxy','ftp_proxy','http_proxy ','https_proxy' - } + 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', + 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR', + 'all_proxy','ftp_proxy','http_proxy ','https_proxy' + } vars_fstypes = { 'btrfs','cpio','cpio.gz','cpio.lz4','cpio.lzma','cpio.xz','cramfs', @@ -2874,7 +2892,7 @@ if toastermain.settings.MANAGED: def projectbuilds(request, pid): template = 'projectbuilds.html' - buildrequests = BuildRequest.objects.exclude(project_id = pid, state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) + buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) try: context, pagesize, orderby = _build_list_helper(request, buildrequests) @@ -3012,6 +3030,14 @@ if toastermain.settings.MANAGED: } return render(request, template, context) + def buildrequestdetails(request, pid, brid): + template = "buildrequestdetails.html" + context = { + 'buildrequest' : BuildRequest.objects.get(pk = brid, project_id = pid) + } + return render(request, template, context) + + else: # these are pages that are NOT available in interactive mode def managedcontextprocessor(request): @@ -3256,3 +3282,6 @@ else: def xhr_updatelayer(request): raise Exception("page not available in interactive mode") + + def buildrequestdetails(request, pid, brid): + raise Exception("page not available in interactive mode")