diff options
7 files changed, 193 insertions, 68 deletions
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): | |||
| 113 | created = models.DateTimeField(auto_now_add = True) | 113 | created = models.DateTimeField(auto_now_add = True) |
| 114 | updated = models.DateTimeField(auto_now = True) | 114 | updated = models.DateTimeField(auto_now = True) |
| 115 | 115 | ||
| 116 | def get_duration(self): | ||
| 117 | return (self.updated - self.created).total_seconds() | ||
| 118 | |||
| 119 | def get_sorted_target_list(self): | ||
| 120 | tgts = self.brtarget_set.order_by( 'target' ); | ||
| 121 | return( tgts ); | ||
| 122 | |||
| 123 | def get_machine(self): | ||
| 124 | return self.brvariable_set.get(name="MACHINE").value | ||
| 116 | 125 | ||
| 117 | # These tables specify the settings for running an actual build. | 126 | # These tables specify the settings for running an actual build. |
| 118 | # They MUST be kept in sync with the tables in orm.models.Project* | 127 | # 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 @@ | |||
| 1 | {% extends "baseprojectpage.html" %} | ||
| 2 | |||
| 3 | {% load static %} | ||
| 4 | {% load projecttags %} | ||
| 5 | {% load humanize %} | ||
| 6 | |||
| 7 | {% block localbreadcrumb %} | ||
| 8 | <li> {{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"}}) </li> | ||
| 9 | {% endblock %} | ||
| 10 | |||
| 11 | {% block projectinfomain %} | ||
| 12 | <!-- begin content --> | ||
| 13 | |||
| 14 | <div class="row-fluid"> | ||
| 15 | |||
| 16 | <!-- end left sidebar container --> | ||
| 17 | <!-- Begin right container --> | ||
| 18 | <div class="span10"> | ||
| 19 | <div class="page-header"> | ||
| 20 | <h1> | ||
| 21 | <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 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%} {{buildrequest.get_machine}} </span> | ||
| 22 | |||
| 23 | </h1> | ||
| 24 | </div> | ||
| 25 | <div class="alert alert-error"> | ||
| 26 | <p class="lead"> | ||
| 27 | <strong>Failed</strong> | ||
| 28 | on {{ buildrequest.updated|date:'d/m/y H:i' }} | ||
| 29 | with | ||
| 30 | |||
| 31 | <i class="icon-minus-sign error" style="margin-left:6px;"></i> | ||
| 32 | <strong><a class="error accordion-toggle toggle-errors" href="#errors"> | ||
| 33 | {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} | ||
| 34 | </a></strong> | ||
| 35 | <span class="pull-right">Build time: {{buildrequest.get_duration|sectohms}}</span> | ||
| 36 | </p> | ||
| 37 | </div> | ||
| 38 | |||
| 39 | <div class="accordion" id="errors" name="errors"> | ||
| 40 | <div class="accordion-group"> | ||
| 41 | <div class="accordion-heading"> | ||
| 42 | <a class="accordion-toggle error toggle-errors"> | ||
| 43 | <h2> | ||
| 44 | <i class="icon-minus-sign"></i> | ||
| 45 | {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} | ||
| 46 | </h2> | ||
| 47 | </a> | ||
| 48 | </div> | ||
| 49 | <div class="accordion-body collapse in" id="collapse-errors"> | ||
| 50 | <div class="accordion-inner"> | ||
| 51 | <div class="span10"> | ||
| 52 | {% for error in buildrequest.brerror_set.all %} | ||
| 53 | <div class="alert alert-error"> | ||
| 54 | ERROR: <div class="air well"><pre>{{error.errmsg}}</pre></div> | ||
| 55 | </div> | ||
| 56 | {% endfor %} | ||
| 57 | </div> | ||
| 58 | </div> | ||
| 59 | </div> | ||
| 60 | |||
| 61 | </div> | ||
| 62 | </div> | ||
| 63 | </div> | ||
| 64 | </div> <!-- end of row-fluid --> | ||
| 65 | |||
| 66 | |||
| 67 | {%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 @@ | |||
| 35 | </div> | 35 | </div> |
| 36 | 36 | ||
| 37 | 37 | ||
| 38 | {% else %} | 38 | {% else %} {# We have builds to display #} |
| 39 | {% include "basetable_top_buildprojects.html" %} | 39 | {% include "basetable_top_buildprojects.html" %} |
| 40 | <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> | 40 | <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> |
| 41 | {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #} | 41 | {% for buildrequest in objects %}{% if buildrequest.build %} {% with build=buildrequest.build %} {# if we have a build, just display it #} |
| 42 | <tr class="data"> | 42 | <tr class="data"> |
| 43 | <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> | 43 | <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> |
| 44 | <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> | 44 | <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> |
| @@ -61,7 +61,7 @@ | |||
| 61 | <td class="errors_no"> | 61 | <td class="errors_no"> |
| 62 | {% if build.errors_no %} | 62 | {% if build.errors_no %} |
| 63 | <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> | 63 | <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> |
| 64 | {% if MANAGED and build.project %} | 64 | {% if MANAGED and build.project and build.buildartifact_set.count %} |
| 65 | <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}"> | 65 | <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}"> |
| 66 | <i class="icon-download-alt" title="" data-original-title="Download build log"></i> | 66 | <i class="icon-download-alt" title="" data-original-title="Download build log"></i> |
| 67 | </a> | 67 | </a> |
| @@ -96,21 +96,21 @@ | |||
| 96 | <tr class="data"> | 96 | <tr class="data"> |
| 97 | <td class="outcome">{% if buildrequest.state == buildrequest.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td> | 97 | <td class="outcome">{% if buildrequest.state == buildrequest.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td> |
| 98 | <td class="target"> | 98 | <td class="target"> |
| 99 | <span data-toggle="tooltip" {%if br.brtarget_set.all.count > 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%} </span> | 99 | <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}"><span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 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%} </span></a> |
| 100 | </td> | 100 | </td> |
| 101 | <td class="machine"> | 101 | <td class="machine"> |
| 102 | {{br.machine}} | 102 | <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.machine}}</a> |
| 103 | </td> | 103 | </td> |
| 104 | <td class="started_on"> | 104 | <td class="started_on"> |
| 105 | {{br.created|date:"d/m/y H:i"}} | 105 | <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.created|date:"d/m/y H:i"}}</a> |
| 106 | </td> | 106 | </td> |
| 107 | <td class="completed_on"> | 107 | <td class="completed_on"> |
| 108 | {{br.updated|date:"d/m/y H:i"}} | 108 | <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.updated|date:"d/m/y H:i"}}</a> |
| 109 | </td> | 109 | </td> |
| 110 | <td class="failed_tasks error"> | 110 | <td class="failed_tasks error"> |
| 111 | {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}} | ||
| 112 | </td> | 111 | </td> |
| 113 | <td class="errors_no"> | 112 | <td class="errors_no"> |
| 113 | <a class="errors_no error" href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}#errors">{{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}}</a> | ||
| 114 | </td> | 114 | </td> |
| 115 | <td class="warnings_no"> | 115 | <td class="warnings_no"> |
| 116 | </td> | 116 | </td> |
| @@ -120,7 +120,7 @@ | |||
| 120 | <td class="output"> {# we have no output here #} | 120 | <td class="output"> {# we have no output here #} |
| 121 | </td> | 121 | </td> |
| 122 | <td class="project"> | 122 | <td class="project"> |
| 123 | <a href="{% url 'project' br.project.id %}">{{br.project.name}}</a> | 123 | <a href="{% url 'project' buildrequest.project.id %}">{{buildrequest.project.name}}</a> |
| 124 | </td> | 124 | </td> |
| 125 | </tr> | 125 | </tr> |
| 126 | {%endif%} | 126 | {%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 @@ | |||
| 26 | <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}"> | 26 | <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}"> |
| 27 | {% endif %} | 27 | {% endif %} |
| 28 | <span data-toggle="tooltip" {%if build.target_set.all.count > 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%} | 28 | <span data-toggle="tooltip" {%if build.target_set.all.count > 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%} |
| 29 | </span> | 29 | </span> |
| 30 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} | 30 | {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} |
| 31 | </a> | 31 | </a> |
| 32 | {% endif %} | 32 | {% endif %} |
| @@ -71,24 +71,42 @@ | |||
| 71 | 71 | ||
| 72 | {% else %} {# we use the project's page recent build design #} | 72 | {% else %} {# we use the project's page recent build design #} |
| 73 | 73 | ||
| 74 | <div class="alert {% if buildrequest.state == buildrequest.REQ_FAILED %}alert-error{% else %}alert-info{% endif %}"> | ||
| 75 | <div class="row-fluid"> | ||
| 76 | 74 | ||
| 77 | 75 | ||
| 76 | |||
| 77 | <div class="alert {% if buildrequest.state == buildrequest.REQ_FAILED %}alert-error{% else %}alert-info{% endif %} project-name"> | ||
| 78 | <span class="label label-danger"> {{buildrequest.project.name}} </span> | ||
| 79 | <div class="row-fluid"> | ||
| 80 | |||
| 78 | {% if buildrequest.state == buildrequest.REQ_FAILED %} | 81 | {% if buildrequest.state == buildrequest.REQ_FAILED %} |
| 79 | <div class="lead span3"> | 82 | <div class="span3 lead"> |
| 80 | <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 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%} </span> | 83 | <a href="{%url 'buildrequestdetails' buildrequest.project.id buildrequest.pk%}" class="error"> |
| 84 | <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 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%} </span> | ||
| 85 | </a> | ||
| 86 | </div> | ||
| 87 | <div class="span2 lead"> | ||
| 88 | {% if buildrequest.updated|format_build_date %} | ||
| 89 | {{ buildrequest.updated|date:'d/m/y H:i' }} | ||
| 90 | {% else %} | ||
| 91 | {{ buildrequest.updated|date:'H:i' }} | ||
| 92 | {% endif %} | ||
| 93 | </div> | ||
| 94 | <div class="span2 lead"> | ||
| 95 | {% if buildrequest.brerror_set.all.count %} | ||
| 96 | <i class="icon-minus-sign red"></i> <a href="{%url 'buildrequestdetails' buildrequest.project.id buildrequest.pk %}#errors" class="error">{{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}}</a> | ||
| 97 | {% endif %} | ||
| 81 | </div> | 98 | </div> |
| 82 | <div > | 99 | <div class="span2 lead"> {# there are no warnings for buildrequests #} |
| 83 | </div> | 100 | </div> |
| 84 | <div class="row-fluid"> | 101 | <div class="lead "> |
| 85 | {% for e in buildrequest.brerror_set.all|slice:":3" %} | 102 | <span class="lead{%if not MANAGED or not buildrequest.project%} pull-right{%endif%}"> |
| 86 | <div class="air well"> | 103 | Build time: <a href="{% url 'buildrequestdetails' buildrequest.project.id buildrequest.pk %}">{{ buildrequest.get_duration|sectohms }}</a> |
| 87 | <pre>{{e.errmsg|whitespace_slice:":150"}}</pre> | 104 | </span> |
| 88 | </div> | 105 | <button class="btn btn-danger pull-right" onclick='scheduleBuild({% url 'xhr_projectbuild' buildrequest.project.id as bpi%}{{bpi|json}}, {{buildrequest.project.name|json}}, {{buildrequest.get_sorted_target_list|mapselect:'target'|json}})'>Run again</button> |
| 89 | {% endfor %} | 106 | |
| 90 | </div> | 107 | </div> |
| 91 | 108 | ||
| 109 | |||
| 92 | {% elif buildrequest.state == buildrequest.REQ_QUEUED %} | 110 | {% elif buildrequest.state == buildrequest.REQ_QUEUED %} |
| 93 | 111 | ||
| 94 | <div class="lead span5"> | 112 | <div class="lead span5"> |
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 @@ | |||
| 13 | No builds found | 13 | No builds found |
| 14 | 14 | ||
| 15 | {% else %} | 15 | {% else %} |
| 16 | {% if request.GET.filter or request.GET.search %} | 16 | {% if request.GET.filter or request.GET.search %} |
| 17 | {{objects.paginator.count}} builds found | 17 | {{objects.paginator.count}} builds found |
| 18 | {% else %} | 18 | {% else %} |
| 19 | Project builds <small>({{objects.paginator.count}})</small> | 19 | Project builds <small>({{objects.paginator.count}})</small> |
| 20 | {% endif %} | 20 | {% endif %} |
| 21 | {% endif %} | 21 | {% endif %} |
| 22 | <i class="icon-question-sign get-help heading-help" title="This page lists all the builds for the current project"></i> | 22 | <i class="icon-question-sign get-help heading-help" title="This page lists all the builds for the current project"></i> |
| 23 | </h1> | 23 | </h1> |
| @@ -89,23 +89,23 @@ | |||
| 89 | 89 | ||
| 90 | 90 | ||
| 91 | <tr class="data"> | 91 | <tr class="data"> |
| 92 | <td class="outcome">{% if buildrequest.state == buildrequest.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td> | 92 | <td class="outcome">{% if br.state == br.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td> |
| 93 | <td class="target"> | 93 | <td class="target"> |
| 94 | <span data-toggle="tooltip" {%if br.brtarget_set.all.count > 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%} </span> | 94 | <a href="{% url "buildrequestdetails" br.project.id br.id %}"><span data-toggle="tooltip" {%if br.brtarget_set.all.count > 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%} </span></a> |
| 95 | </td> | 95 | </td> |
| 96 | <td class="machine"> | 96 | <td class="machine"> |
| 97 | {{br.machine}} | 97 | <a href="{% url "buildrequestdetails" br.project.id br.id %}">{{br.machine}}</a> |
| 98 | </td> | 98 | </td> |
| 99 | <td class="started_on"> | 99 | <td class="started_on"> |
| 100 | {{br.created|date:"d/m/y H:i"}} | 100 | <a href="{% url "buildrequestdetails" br.project.id br.id %}">{{br.created|date:"d/m/y H:i"}}</a> |
| 101 | </td> | 101 | </td> |
| 102 | <td class="completed_on"> | 102 | <td class="completed_on"> |
| 103 | {{br.updated|date:"d/m/y H:i"}} | 103 | <a href="{% url "buildrequestdetails" br.project.id br.id %}">{{br.updated|date:"d/m/y H:i"}}</a> |
| 104 | </td> | 104 | </td> |
| 105 | <td class="failed_tasks error"> | 105 | <td class="failed_tasks error"> |
| 106 | {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}} | ||
| 107 | </td> | 106 | </td> |
| 108 | <td class="errors_no"> | 107 | <td class="errors_no"> |
| 108 | <a class="errors_no error" href="{% url "buildrequestdetails" br.project.id br.id %}#errors">{{br.brerror_set.all.count}} error{{br.brerror_set.all.count|pluralize}}</a> | ||
| 109 | </td> | 109 | </td> |
| 110 | <td class="warnings_no"> | 110 | <td class="warnings_no"> |
| 111 | </td> | 111 | </td> |
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', | |||
| 97 | url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), | 97 | url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), |
| 98 | url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'), | 98 | url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'), |
| 99 | 99 | ||
| 100 | # dashboard for failed build requests | ||
| 101 | url(r'^project/(?P<pid>\d+)/buildrequest/(?P<brid>\d+)$', 'buildrequestdetails', name='buildrequestdetails'), | ||
| 100 | 102 | ||
| 101 | # default redirection | 103 | # default redirection |
| 102 | url(r'^$', RedirectView.as_view( url= 'landing')), | 104 | 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 @@ | |||
| 22 | import operator,re | 22 | import operator,re |
| 23 | import HTMLParser | 23 | import HTMLParser |
| 24 | 24 | ||
| 25 | from django.db.models import Q, Sum | 25 | from django.db.models import Q, Sum, Count |
| 26 | from django.db import IntegrityError | 26 | from django.db import IntegrityError |
| 27 | from django.shortcuts import render, redirect | 27 | from django.shortcuts import render, redirect |
| 28 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable | 28 | 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): | |||
| 117 | return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs) | 117 | return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs) |
| 118 | 118 | ||
| 119 | FIELD_SEPARATOR = ":" | 119 | FIELD_SEPARATOR = ":" |
| 120 | VALUE_SEPARATOR = "!" | 120 | AND_VALUE_SEPARATOR = "!" |
| 121 | OR_VALUE_SEPARATOR = "|" | ||
| 121 | DESCENDING = "-" | 122 | DESCENDING = "-" |
| 122 | 123 | ||
| 123 | def __get_q_for_val(name, value): | 124 | def __get_q_for_val(name, value): |
| @@ -126,20 +127,31 @@ def __get_q_for_val(name, value): | |||
| 126 | if "AND" in value: | 127 | if "AND" in value: |
| 127 | return reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ])) | 128 | return reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ])) |
| 128 | if value.startswith("NOT"): | 129 | if value.startswith("NOT"): |
| 129 | kwargs = { name : value.strip("NOT") } | 130 | value = value[3:] |
| 131 | if value == 'None': | ||
| 132 | value = None | ||
| 133 | kwargs = { name : value } | ||
| 130 | return ~Q(**kwargs) | 134 | return ~Q(**kwargs) |
| 131 | else: | 135 | else: |
| 136 | if value == 'None': | ||
| 137 | value = None | ||
| 132 | kwargs = { name : value } | 138 | kwargs = { name : value } |
| 133 | return Q(**kwargs) | 139 | return Q(**kwargs) |
| 134 | 140 | ||
| 135 | def _get_filtering_query(filter_string): | 141 | def _get_filtering_query(filter_string): |
| 136 | 142 | ||
| 137 | search_terms = filter_string.split(FIELD_SEPARATOR) | 143 | search_terms = filter_string.split(FIELD_SEPARATOR) |
| 138 | keys = search_terms[0].split(VALUE_SEPARATOR) | 144 | and_keys = search_terms[0].split(AND_VALUE_SEPARATOR) |
| 139 | values = search_terms[1].split(VALUE_SEPARATOR) | 145 | and_values = search_terms[1].split(AND_VALUE_SEPARATOR) |
| 146 | |||
| 147 | and_query = [] | ||
| 148 | for kv in zip(and_keys, and_values): | ||
| 149 | or_keys = kv[0].split(OR_VALUE_SEPARATOR) | ||
| 150 | or_values = kv[1].split(OR_VALUE_SEPARATOR) | ||
| 151 | querydict = dict(zip(or_keys, or_values)) | ||
| 152 | and_query.append(reduce(operator.or_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict]))) | ||
| 140 | 153 | ||
| 141 | querydict = dict(zip(keys, values)) | 154 | return reduce(operator.and_, [k for k in and_query]) |
| 142 | return reduce(operator.and_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict])) | ||
| 143 | 155 | ||
| 144 | def _get_toggle_order(request, orderkey, reverse = False): | 156 | def _get_toggle_order(request, orderkey, reverse = False): |
| 145 | if reverse: | 157 | if reverse: |
| @@ -169,13 +181,13 @@ def _validate_input(input, model): | |||
| 169 | return None, invalid | 181 | return None, invalid |
| 170 | 182 | ||
| 171 | # Check we have an equal number of terms both sides of the colon | 183 | # Check we have an equal number of terms both sides of the colon |
| 172 | if len(input_list[0].split(VALUE_SEPARATOR)) != len(input_list[1].split(VALUE_SEPARATOR)): | 184 | if len(input_list[0].split(AND_VALUE_SEPARATOR)) != len(input_list[1].split(AND_VALUE_SEPARATOR)): |
| 173 | invalid = "Not all arg names got values" | 185 | invalid = "Not all arg names got values" |
| 174 | return None, invalid + str(input_list) | 186 | return None, invalid + str(input_list) |
| 175 | 187 | ||
| 176 | # Check we are looking for a valid field | 188 | # Check we are looking for a valid field |
| 177 | valid_fields = model._meta.get_all_field_names() | 189 | valid_fields = model._meta.get_all_field_names() |
| 178 | for field in input_list[0].split(VALUE_SEPARATOR): | 190 | for field in input_list[0].split(AND_VALUE_SEPARATOR): |
| 179 | if not reduce(lambda x, y: x or y, map(lambda x: field.startswith(x), [ x for x in valid_fields ])): | 191 | if not reduce(lambda x, y: x or y, map(lambda x: field.startswith(x), [ x for x in valid_fields ])): |
| 180 | return None, (field, [ x for x in valid_fields ]) | 192 | return None, (field, [ x for x in valid_fields ]) |
| 181 | 193 | ||
| @@ -216,6 +228,7 @@ def _search_tuple(request, model): | |||
| 216 | def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''): | 228 | def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''): |
| 217 | if filter_string: | 229 | if filter_string: |
| 218 | filter_query = _get_filtering_query(filter_string) | 230 | filter_query = _get_filtering_query(filter_string) |
| 231 | # raise Exception(filter_query) | ||
| 219 | queryset = queryset.filter(filter_query) | 232 | queryset = queryset.filter(filter_query) |
| 220 | else: | 233 | else: |
| 221 | queryset = queryset.all() | 234 | queryset = queryset.all() |
| @@ -1780,12 +1793,13 @@ if toastermain.settings.MANAGED: | |||
| 1780 | # for that object type. copypasta for all needed table searches | 1793 | # for that object type. copypasta for all needed table searches |
| 1781 | (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest) | 1794 | (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest) |
| 1782 | # we don't display in-progress or deleted builds | 1795 | # we don't display in-progress or deleted builds |
| 1783 | queryset_all = buildrequests | 1796 | queryset_all = buildrequests.exclude(state = BuildRequest.REQ_DELETED) |
| 1784 | queryset_with_search = _get_queryset(BuildRequest, queryset_all, None, search_term, ordering_string, '-updated') | 1797 | queryset_all = queryset_all.annotate(Count('brerror')) |
| 1785 | queryset = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') | 1798 | queryset_with_search = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') |
| 1799 | |||
| 1786 | 1800 | ||
| 1787 | # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display | 1801 | # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display |
| 1788 | build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1)) | 1802 | build_info = _build_page_range(Paginator(queryset_with_search, pagesize), request.GET.get('page', 1)) |
| 1789 | 1803 | ||
| 1790 | # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) | 1804 | # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) |
| 1791 | # most recent build is like projects' most recent builds, but across all projects | 1805 | # most recent build is like projects' most recent builds, but across all projects |
| @@ -1842,8 +1856,8 @@ if toastermain.settings.MANAGED: | |||
| 1842 | 'filter' : {'class' : 'outcome', | 1856 | 'filter' : {'class' : 'outcome', |
| 1843 | 'label': 'Show:', | 1857 | 'label': 'Show:', |
| 1844 | 'options' : [ | 1858 | 'options' : [ |
| 1845 | ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_with_search.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression | 1859 | ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_all.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression |
| 1846 | ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_with_search.filter(state=str(BuildRequest.REQ_FAILED)).count()), | 1860 | ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_all.filter(state=str(BuildRequest.REQ_FAILED)).count()), |
| 1847 | ] | 1861 | ] |
| 1848 | } | 1862 | } |
| 1849 | }, | 1863 | }, |
| @@ -1865,9 +1879,9 @@ if toastermain.settings.MANAGED: | |||
| 1865 | 'filter' : {'class' : 'created', | 1879 | 'filter' : {'class' : 'created', |
| 1866 | 'label': 'Show:', | 1880 | 'label': 'Show:', |
| 1867 | 'options' : [ | 1881 | 'options' : [ |
| 1868 | ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=timezone.now()).count()), | 1882 | ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(created__gte=timezone.now()).count()), |
| 1869 | ("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()), | 1883 | ("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()), |
| 1870 | ("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()), | 1884 | ("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()), |
| 1871 | ] | 1885 | ] |
| 1872 | } | 1886 | } |
| 1873 | }, | 1887 | }, |
| @@ -1879,9 +1893,9 @@ if toastermain.settings.MANAGED: | |||
| 1879 | 'filter' : {'class' : 'updated', | 1893 | 'filter' : {'class' : 'updated', |
| 1880 | 'label': 'Show:', | 1894 | 'label': 'Show:', |
| 1881 | 'options' : [ | 1895 | 'options' : [ |
| 1882 | ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=timezone.now()).count()), | 1896 | ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=timezone.now()).count()), |
| 1883 | ("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()), | 1897 | ("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()), |
| 1884 | ("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()), | 1898 | ("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()), |
| 1885 | ] | 1899 | ] |
| 1886 | } | 1900 | } |
| 1887 | }, | 1901 | }, |
| @@ -1890,8 +1904,10 @@ if toastermain.settings.MANAGED: | |||
| 1890 | 'filter' : {'class' : 'failed_tasks', | 1904 | 'filter' : {'class' : 'failed_tasks', |
| 1891 | 'label': 'Show:', | 1905 | 'label': 'Show:', |
| 1892 | 'options' : [ | 1906 | 'options' : [ |
| 1893 | ('Build with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()), | 1907 | ('Builds with failed tasks', 'build__task_build__outcome:%d' % Task.OUTCOME_FAILED, |
| 1894 | ('Build without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()), | 1908 | queryset_all.filter(build__task_build__outcome=Task.OUTCOME_FAILED).count()), |
| 1909 | ('Builds without failed tasks', 'build__task_build__outcome:%d' % Task.OUTCOME_FAILED, | ||
| 1910 | queryset_all.filter(~Q(build__task_build__outcome=Task.OUTCOME_FAILED)).count()), | ||
| 1895 | ] | 1911 | ] |
| 1896 | } | 1912 | } |
| 1897 | }, | 1913 | }, |
| @@ -1903,8 +1919,10 @@ if toastermain.settings.MANAGED: | |||
| 1903 | 'filter' : {'class' : 'errors_no', | 1919 | 'filter' : {'class' : 'errors_no', |
| 1904 | 'label': 'Show:', | 1920 | 'label': 'Show:', |
| 1905 | 'options' : [ | 1921 | 'options' : [ |
| 1906 | ('Build with errors', 'build__errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()), | 1922 | ('Builds with errors', 'build|build__errors_no__gt:None|0', |
| 1907 | ('Build without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()), | 1923 | queryset_all.filter(Q(build=None) | Q(build__errors_no__gt=0)).count()), |
| 1924 | ('Builds without errors', 'build__errors_no:0', | ||
| 1925 | queryset_all.filter(build__errors_no=0).count()), | ||
| 1908 | ] | 1926 | ] |
| 1909 | } | 1927 | } |
| 1910 | }, | 1928 | }, |
| @@ -1916,8 +1934,8 @@ if toastermain.settings.MANAGED: | |||
| 1916 | 'filter' : {'class' : 'build__warnings_no', | 1934 | 'filter' : {'class' : 'build__warnings_no', |
| 1917 | 'label': 'Show:', | 1935 | 'label': 'Show:', |
| 1918 | 'options' : [ | 1936 | 'options' : [ |
| 1919 | ('Build with warnings','build__warnings_no__gte:1', queryset_with_search.filter(build__warnings_no__gte=1).count()), | 1937 | ('Builds with warnings','build__warnings_no__gte:1', queryset_all.filter(build__warnings_no__gte=1).count()), |
| 1920 | ('Build without warnings','build__warnings_no:0', queryset_with_search.filter(build__warnings_no=0).count()), | 1938 | ('Builds without warnings','build__warnings_no:0', queryset_all.filter(build__warnings_no=0).count()), |
| 1921 | ] | 1939 | ] |
| 1922 | } | 1940 | } |
| 1923 | }, | 1941 | }, |
| @@ -2016,7 +2034,7 @@ if toastermain.settings.MANAGED: | |||
| 2016 | 2034 | ||
| 2017 | context = { | 2035 | context = { |
| 2018 | "project" : prj, | 2036 | "project" : prj, |
| 2019 | "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS), | 2037 | "completedbuilds": BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED), |
| 2020 | "prj" : {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}}, | 2038 | "prj" : {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}}, |
| 2021 | #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), | 2039 | #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), |
| 2022 | "builds" : _project_recent_build_list(prj), | 2040 | "builds" : _project_recent_build_list(prj), |
| @@ -2061,7 +2079,7 @@ if toastermain.settings.MANAGED: | |||
| 2061 | try: | 2079 | try: |
| 2062 | if request.method != "POST": | 2080 | if request.method != "POST": |
| 2063 | raise BadParameterException("invalid method") | 2081 | raise BadParameterException("invalid method") |
| 2064 | request.session['project_id'] = pid | 2082 | request.session['project_id'] = pid |
| 2065 | prj = Project.objects.get(id = pid) | 2083 | prj = Project.objects.get(id = pid) |
| 2066 | 2084 | ||
| 2067 | 2085 | ||
| @@ -2167,11 +2185,11 @@ if toastermain.settings.MANAGED: | |||
| 2167 | try: | 2185 | try: |
| 2168 | prj = None | 2186 | prj = None |
| 2169 | if request.GET.has_key('project_id'): | 2187 | if request.GET.has_key('project_id'): |
| 2170 | prj = Project.objects.get(pk = request.GET['project_id']) | 2188 | prj = Project.objects.get(pk = request.GET['project_id']) |
| 2171 | elif 'project_id' in request.session: | 2189 | elif 'project_id' in request.session: |
| 2172 | prj = Project.objects.get(pk = request.session['project_id']) | 2190 | prj = Project.objects.get(pk = request.session['project_id']) |
| 2173 | else: | 2191 | else: |
| 2174 | raise Exception("No valid project selected") | 2192 | raise Exception("No valid project selected") |
| 2175 | 2193 | ||
| 2176 | 2194 | ||
| 2177 | def _lv_to_dict(x): | 2195 | def _lv_to_dict(x): |
| @@ -2819,10 +2837,10 @@ if toastermain.settings.MANAGED: | |||
| 2819 | 2837 | ||
| 2820 | vars_blacklist = { | 2838 | vars_blacklist = { |
| 2821 | 'DL_DR','PARALLEL_MAKE','BB_NUMBER_THREADS','SSTATE_DIR', | 2839 | 'DL_DR','PARALLEL_MAKE','BB_NUMBER_THREADS','SSTATE_DIR', |
| 2822 | 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', | 2840 | 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', |
| 2823 | 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR', | 2841 | 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR', |
| 2824 | 'all_proxy','ftp_proxy','http_proxy ','https_proxy' | 2842 | 'all_proxy','ftp_proxy','http_proxy ','https_proxy' |
| 2825 | } | 2843 | } |
| 2826 | 2844 | ||
| 2827 | vars_fstypes = { | 2845 | vars_fstypes = { |
| 2828 | 'btrfs','cpio','cpio.gz','cpio.lz4','cpio.lzma','cpio.xz','cramfs', | 2846 | 'btrfs','cpio','cpio.gz','cpio.lz4','cpio.lzma','cpio.xz','cramfs', |
| @@ -2874,7 +2892,7 @@ if toastermain.settings.MANAGED: | |||
| 2874 | 2892 | ||
| 2875 | def projectbuilds(request, pid): | 2893 | def projectbuilds(request, pid): |
| 2876 | template = 'projectbuilds.html' | 2894 | template = 'projectbuilds.html' |
| 2877 | buildrequests = BuildRequest.objects.exclude(project_id = pid, state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) | 2895 | buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) |
| 2878 | 2896 | ||
| 2879 | try: | 2897 | try: |
| 2880 | context, pagesize, orderby = _build_list_helper(request, buildrequests) | 2898 | context, pagesize, orderby = _build_list_helper(request, buildrequests) |
| @@ -3012,6 +3030,14 @@ if toastermain.settings.MANAGED: | |||
| 3012 | } | 3030 | } |
| 3013 | return render(request, template, context) | 3031 | return render(request, template, context) |
| 3014 | 3032 | ||
| 3033 | def buildrequestdetails(request, pid, brid): | ||
| 3034 | template = "buildrequestdetails.html" | ||
| 3035 | context = { | ||
| 3036 | 'buildrequest' : BuildRequest.objects.get(pk = brid, project_id = pid) | ||
| 3037 | } | ||
| 3038 | return render(request, template, context) | ||
| 3039 | |||
| 3040 | |||
| 3015 | else: | 3041 | else: |
| 3016 | # these are pages that are NOT available in interactive mode | 3042 | # these are pages that are NOT available in interactive mode |
| 3017 | def managedcontextprocessor(request): | 3043 | def managedcontextprocessor(request): |
| @@ -3256,3 +3282,6 @@ else: | |||
| 3256 | 3282 | ||
| 3257 | def xhr_updatelayer(request): | 3283 | def xhr_updatelayer(request): |
| 3258 | raise Exception("page not available in interactive mode") | 3284 | raise Exception("page not available in interactive mode") |
| 3285 | |||
| 3286 | def buildrequestdetails(request, pid, brid): | ||
| 3287 | raise Exception("page not available in interactive mode") | ||
