summaryrefslogtreecommitdiffstats
path: root/bitbake
diff options
context:
space:
mode:
authorAlexandru DAMIAN <alexandru.damian@intel.com>2015-01-14 12:46:54 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-01-16 08:25:31 +0000
commit2d78912bc62b7452433e3c785327d542449e3011 (patch)
tree279414cc98424924973d6aa0d378a3b8396b24f3 /bitbake
parentce784879f48086ba8d33d7589293afbaf94780a0 (diff)
downloadpoky-2d78912bc62b7452433e3c785327d542449e3011.tar.gz
bitbake: toastergui: all builds page lists failed build requests
This patch modifies the all builds page by splitting the page into two variants - the "interactive" (default) and "managed" mode versions. In the "managed" mode version, we display build requests instead of builds, including the failed build requests that have no build associated with them. [YOCTO #6671] (Bitbake rev: c5f5fb80308228585aa7ff9721352feb5ed9c961) Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake')
-rw-r--r--bitbake/lib/toaster/toastergui/templates/managed_builds.html135
-rw-r--r--bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html172
-rw-r--r--bitbake/lib/toaster/toastergui/templatetags/projecttags.py19
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py615
4 files changed, 742 insertions, 199 deletions
diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
new file mode 100644
index 0000000000..5944dc4747
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
@@ -0,0 +1,135 @@
1{% extends "base.html" %}
2
3{% load static %}
4{% load projecttags %}
5{% load humanize %}
6
7{% block pagecontent %}
8<div class="row-fluid">
9
10 {% include "managed_mrb_section.html" %}
11
12
13 {% if 1 %}
14 <div class="page-header top-air">
15 <h1>
16 {% if request.GET.filter and objects.paginator.count > 0 or request.GET.search and objects.paginator.count > 0 %}
17 {{objects.paginator.count}} build{{objects.paginator.count|pluralize}} found
18 {%elif request.GET.filter and objects.paginator.count == 0 or request.GET.search and objects.paginator.count == 0 %}
19 No builds found
20 {%else%}
21 All builds
22 {%endif%}
23 </h1>
24 </div>
25
26 {% if objects.paginator.count == 0 %}
27 <div class="row-fluid">
28 <div class="alert">
29 <form class="no-results input-append" id="searchform">
30 <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %}
31 <button class="btn" type="submit" value="Search">Search</button>
32 <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all builds</button>
33 </form>
34 </div>
35 </div>
36
37
38 {% else %}
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 -->
41 {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #}
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>
44 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
45 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
46 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
47 <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td>
48 <td class="failed_tasks error">
49 {% query build.task_build outcome=4 order__gt=0 as exectask%}
50 {% if exectask.count == 1 %}
51 <a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a>
52 {% if MANAGED and build.project %}
53 <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}">
54 <i class="icon-download-alt" title="" data-original-title="Download task log file"></i>
55 </a>
56 {% endif %}
57 {% elif exectask.count > 1%}
58 <a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}} task{{exectask.count|pluralize}}</a>
59 {%endif%}
60 </td>
61 <td class="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>
64 {% if MANAGED and build.project %}
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>
67 </a>
68 {% endif %}
69 {%endif%}
70 </td>
71 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
72 <td class="time"><a href="{% url "buildtime" build.id %}">{{build.timespent|sectohms}}</a></td>
73 {% if not MANAGED or not build.project %}
74 <td class="log">{{build.cooker_log_path}}</td>
75 {% endif %}
76 <td class="output">
77 {% if build.outcome == build.SUCCEEDED %}
78 <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
79 {% endif %}
80 </td>
81 {% if MANAGED %}
82 <td class="project">
83 {% if build.project %}
84 <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a>
85 {% endif %}
86 </td>
87 {% endif %}
88 </tr>
89
90
91 {%endwith%}
92 {% else %} {# we don't have a build for this build request, mask the data with build request data #}
93
94
95
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>
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>
100 </td>
101 <td class="machine">
102 {{br.machine}}
103 </td>
104 <td class="started_on">
105 {{br.created|date:"d/m/y H:i"}}
106 </td>
107 <td class="completed_on">
108 {{br.updated|date:"d/m/y H:i"}}
109 </td>
110 <td class="failed_tasks error">
111 {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}}
112 </td>
113 <td class="errors_no">
114 </td>
115 <td class="warnings_no">
116 </td>
117 <td class="time">
118 {{br.timespent.total_seconds|sectohms}}
119 </td>
120 <td class="output"> {# we have no output here #}
121 </td>
122 <td class="project">
123 <a href="{% url 'project' br.project.id %}">{{br.project.name}}</a>
124 </td>
125 </tr>
126 {%endif%}
127 {% endfor %}
128
129
130 {% include "basetable_bottom.html" %}
131 {% endif %} {# objects.paginator.count #}
132{% endif %} {# empty #}
133</div><!-- end row-fluid-->
134
135{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html
new file mode 100644
index 0000000000..d4959a0b52
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html
@@ -0,0 +1,172 @@
1{% load static %}
2{% load projecttags %}
3{% load humanize %}
4
5
6{%if mru.count > 0%}
7
8 <div class="page-header top-air">
9 <h1>
10 Latest builds
11 </h1>
12 </div>
13 <div id="latest-builds">
14 {% for buildrequest in mru %}{% with build=buildrequest.build %}
15
16 {% if build %} {# if we have a build, just display it #}
17
18 <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%} {% if MANAGED and build.project %}project-name{% endif %} ">
19 {% if MANAGED and build.project %}
20 <span class="label {%if build.outcome == build.SUCCEEDED%}label-success{%elif build.outcome == build.FAILED%}label-danger{%else%}label-info{%endif%}"> {{build.project.name}} </span>
21 {% endif %}
22
23 <div class="row-fluid">
24 <div class="span3 lead">
25 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
26 <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%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%}
29 </span>
30 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
31 </a>
32 {% endif %}
33 </div>
34 <div class="span2 lead">
35 {% if build.completed_on|format_build_date %}
36 {{ build.completed_on|date:'d/m/y H:i' }}
37 {% else %}
38 {{ build.completed_on|date:'H:i' }}
39 {% endif %}
40 </div>
41 {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
42 <div class="span2 lead">
43 {% if build.errors_no %}
44 <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}#errors" class="error">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
45 {% endif %}
46 </div>
47 <div class="span2 lead">
48 {% if build.warnings_no %}
49 <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}#warnings" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>
50 {% endif %}
51 </div>
52 <div class="lead ">
53 <span class="lead{%if not MANAGED or not build.project%} pull-right{%endif%}">
54 Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a>
55 </span>
56 {% if MANAGED and build.project %}
57 <a class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%elif build.outcome == build.FAILED%}btn-danger{%else%}btn-info{%endif%} pull-right" onclick="scheduleBuild({% url 'xhr_projectbuild' build.project.id as bpi%}{{bpi|json}}, {{build.project.name|json}}, {{build.get_sorted_target_list|mapselect:'target'|json}})">Run again</a>
58 {% endif %}
59 </div>
60 {%endif%}
61 {%if build.outcome == build.IN_PROGRESS %}
62 <div class="span4">
63 <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete">
64 <div style="width: {{build.completeper}}%;" class="bar"></div>
65 </div>
66 </div>
67 <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div>
68 {%endif%}
69 </div>
70 </div>
71
72 {% else %} {# we use the project's page recent build design #}
73
74 <div class="alert {% if buildrequest.state == buildrequest.REQ_FAILED %}alert-error{% else %}alert-info{% endif %}">
75 <div class="row-fluid">
76
77
78 {% if buildrequest.state == buildrequest.REQ_FAILED %}
79 <div class="lead span3">
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>
81 </div>
82 <div >
83 </div>
84 <div class="row-fluid">
85 {% for e in buildrequest.brerror_set.all|slice:":3" %}
86 <div class="air well">
87 <pre>{{e.errmsg|whitespace_slice:":150"}}</pre>
88 </div>
89 {% endfor %}
90 </div>
91
92 {% elif buildrequest.state == buildrequest.REQ_QUEUED %}
93
94 <div class="lead span5">
95
96 <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>
97 </div>
98 <div class="span4 lead" >Build queued
99 <i title="This build will start as soon as a build server is available" class="icon-question-sign get-help get-help-blue heading-help" data-toggle="tooltip"></i>
100 </div>
101
102 {% elif buildrequest.state == buildrequest.REQ_CREATED %}
103
104 <div class="lead span3">
105 <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>
106 </div>
107 <div class="span6" >
108 <span class="lead">Creating build</span>
109 </div>
110
111 {% elif buildrequest.state == buildrequest.REQ_INPROGRESS %}
112
113 <div class="lead span5">
114 <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>
115 </div>
116 <div class="span4 lead">
117 Checking out layers
118 </div>
119 {% else %}
120
121 <div>FIXME!</div>
122
123 {% endif %}
124 <div class="lead pull-right">
125 </div>
126 </div>
127 </div>
128
129
130
131 {% endif %} {# this ends the build request most recent build section #}
132
133{%endwith%}{% endfor %}
134 </div>
135
136<script>
137
138function _makeXHRBuildCall(url, data, onsuccess, onfail) {
139 $.ajax( {
140 type: "POST",
141 url: url,
142 data: data,
143 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
144 success: function (_data) {
145 if (_data.error != "ok") {
146 alert(_data.error);
147 } else {
148 if (onsuccess != undefined) onsuccess(_data);
149 }
150 },
151 error: function (_data) {
152 alert("Call failed");
153 console.log(_data);
154 if (onfail) onfail(data);
155 } });
156}
157
158
159function scheduleBuild(url, projectName, buildlist) {
160 console.log("scheduleBuild");
161 _makeXHRBuildCall(url, {targets: buildlist.join(" ")}, function (_data) {
162
163 $('#latest-builds').prepend('<div class="alert alert-info" style="padding-top:0px">' + '<span class="label label-info" style="font-weight: normal; margin-bottom: 5px; margin-left:-15px; padding-top:5px;">'+projectName+'</span><div class="row-fluid">' +
164 '<div class="span4 lead">' + buildlist.join(" ") +
165 '</div><div class="span4 lead pull-right">Build queued. Your build will start shortly.</div></div></div>');
166 });
167}
168
169</script>
170
171{%endif%}
172
diff --git a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py
index f564edfe49..276c6eb098 100644
--- a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py
@@ -65,6 +65,25 @@ def query(qs, **kwargs):
65 """ 65 """
66 return qs.filter(**kwargs) 66 return qs.filter(**kwargs)
67 67
68
69@register.filter("whitespace_slice")
70def whitespace_space_filter(value, arg):
71 try:
72 bits = []
73 for x in arg.split(":"):
74 if len(x) == 0:
75 bits.append(None)
76 else:
77 # convert numeric value to the first whitespace after
78 first_whitespace = value.find(" ", int(x))
79 if first_whitespace == -1:
80 bits.append(int(x))
81 else:
82 bits.append(first_whitespace)
83 return value[slice(*bits)]
84 except (ValueError, TypeError):
85 raise
86
68@register.filter 87@register.filter
69def divide(value, arg): 88def divide(value, arg):
70 if int(arg) == 0: 89 if int(arg) == 0:
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index e414b66480..e8e4927b7e 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -255,197 +255,6 @@ def _save_parameters_cookies(response, pagesize, orderby, request):
255 return response 255 return response
256 256
257 257
258
259# shows the "all builds" page
260def builds(request):
261 template = 'build.html'
262 # define here what parameters the view needs in the GET portion in order to
263 # be able to display something. 'count' and 'page' are mandatory for all views
264 # that use paginators.
265 (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
266 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
267 retval = _verify_parameters( request.GET, mandatory_parameters )
268 if retval:
269 return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
270
271 # boilerplate code that takes a request for an object type and returns a queryset
272 # for that object type. copypasta for all needed table searches
273 (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
274 queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS)
275 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
276 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
277
278 # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
279 build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
280
281 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
282 build_mru = Build.objects.order_by("-started_on")[:3]
283
284 # set up list of fstypes for each build
285 fstypes_map = {};
286 for build in build_info:
287 targets = Target.objects.filter( build_id = build.id )
288 comma = "";
289 extensions = "";
290 for t in targets:
291 if ( not t.is_image ):
292 continue
293 tif = Target_Image_File.objects.filter( target_id = t.id )
294 for i in tif:
295 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
296 if s == i.file_name:
297 s=re.sub('.*\.', '', i.file_name)
298 if None == re.search(s,extensions):
299 extensions += comma + s
300 comma = ", "
301 fstypes_map[build.id]=extensions
302
303 # send the data to the template
304 context = {
305 # specific info for
306 'mru' : build_mru,
307 # TODO: common objects for all table views, adapt as needed
308 'objects' : build_info,
309 'objectname' : "builds",
310 'default_orderby' : 'completed_on:-',
311 'fstypes' : fstypes_map,
312 'search_term' : search_term,
313 'total_count' : queryset_with_search.count(),
314 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
315 'tablecols' : [
316 {'name': 'Outcome', # column with a single filter
317 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
318 'dclass' : "span2", # indication about column width; comes from the design
319 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
320 'ordericon':_get_toggle_order_icon(request, "outcome"),
321 # filter field will set a filter on that column with the specs in the filter description
322 # the class field in the filter has no relation with clclass; the control different aspects of the UI
323 # still, it is recommended for the values to be identical for easy tracking in the generated HTML
324 'filter' : {'class' : 'outcome',
325 'label': 'Show:',
326 'options' : [
327 ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression
328 ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
329 ]
330 }
331 },
332 {'name': 'Target', # default column, disabled box, with just the name in the list
333 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
334 'orderfield': _get_toggle_order(request, "target__target"),
335 'ordericon':_get_toggle_order_icon(request, "target__target"),
336 },
337 {'name': 'Machine',
338 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
339 'orderfield': _get_toggle_order(request, "machine"),
340 'ordericon':_get_toggle_order_icon(request, "machine"),
341 'dclass': 'span3'
342 }, # a slightly wider column
343 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
344 'qhelp': "The date and time you started the build",
345 'orderfield': _get_toggle_order(request, "started_on", True),
346 'ordericon':_get_toggle_order_icon(request, "started_on"),
347 'filter' : {'class' : 'started_on',
348 'label': 'Show:',
349 'options' : [
350 ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()),
351 ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()),
352 ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()),
353 ]
354 }
355 },
356 {'name': 'Completed on',
357 'qhelp': "The date and time the build finished",
358 'orderfield': _get_toggle_order(request, "completed_on", True),
359 'ordericon':_get_toggle_order_icon(request, "completed_on"),
360 'orderkey' : 'completed_on',
361 'filter' : {'class' : 'completed_on',
362 'label': 'Show:',
363 'options' : [
364 ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()),
365 ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).count()),
366 ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()),
367 ]
368 }
369 },
370 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
371 'qhelp': "How many tasks failed during the build",
372 'filter' : {'class' : 'failed_tasks',
373 'label': 'Show:',
374 'options' : [
375 ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
376 ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
377 ]
378 }
379 },
380 {'name': 'Errors', 'clclass': 'errors_no',
381 'qhelp': "How many errors were encountered during the build (if any)",
382 'orderfield': _get_toggle_order(request, "errors_no", True),
383 'ordericon':_get_toggle_order_icon(request, "errors_no"),
384 'orderkey' : 'errors_no',
385 'filter' : {'class' : 'errors_no',
386 'label': 'Show:',
387 'options' : [
388 ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
389 ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
390 ]
391 }
392 },
393 {'name': 'Warnings', 'clclass': 'warnings_no',
394 'qhelp': "How many warnings were encountered during the build (if any)",
395 'orderfield': _get_toggle_order(request, "warnings_no", True),
396 'ordericon':_get_toggle_order_icon(request, "warnings_no"),
397 'orderkey' : 'warnings_no',
398 'filter' : {'class' : 'warnings_no',
399 'label': 'Show:',
400 'options' : [
401 ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
402 ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
403 ]
404 }
405 },
406 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
407 'qhelp': "How long it took the build to finish",
408 'orderfield': _get_toggle_order(request, "timespent", True),
409 'ordericon':_get_toggle_order_icon(request, "timespent"),
410 'orderkey' : 'timespent',
411 },
412 {'name': 'Image files', 'clclass': 'output',
413 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
414 # TODO: compute image fstypes from Target_Image_File
415 },
416 ]
417 }
418
419 if not toastermain.settings.MANAGED:
420 context['tablecols'].insert(-2,
421 {'name': 'Log1',
422 'dclass': "span4",
423 'qhelp': "Path to the build main log file",
424 'clclass': 'log', 'hidden': 1,
425 'orderfield': _get_toggle_order(request, "cooker_log_path"),
426 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
427 'orderkey' : 'cooker_log_path',
428 }
429 )
430
431
432 if toastermain.settings.MANAGED:
433 context['tablecols'].append(
434 {'name': 'Project', 'clclass': 'project',
435 'filter': {'class': 'project',
436 'label': 'Project:',
437 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()),
438
439 }
440 }
441 )
442
443
444 response = render(request, template, context)
445 _save_parameters_cookies(response, pagesize, orderby, request)
446 return response
447
448
449## 258##
450# build dashboard for a single build, coming in as argument 259# build dashboard for a single build, coming in as argument
451# Each build may contain multiple targets and each target 260# Each build may contain multiple targets and each target
@@ -1895,6 +1704,221 @@ if toastermain.settings.MANAGED:
1895 del request.session['project_id'] 1704 del request.session['project_id']
1896 return ret 1705 return ret
1897 1706
1707
1708 # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds
1709 def builds(request):
1710 template = 'managed_builds.html'
1711 # define here what parameters the view needs in the GET portion in order to
1712 # be able to display something. 'count' and 'page' are mandatory for all views
1713 # that use paginators.
1714
1715 # ATTN: we use here the ordering parameters for interactive mode; the translation for BuildRequest fields will happen below
1716 (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
1717 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1718 retval = _verify_parameters( request.GET, mandatory_parameters )
1719 if retval:
1720 return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
1721
1722 # translate interactive mode ordering to managed mode ordering
1723 ordering_params = orderby.split(":")
1724 if ordering_params[0] == "completed_on":
1725 ordering_params[0] = "updated"
1726 if ordering_params[0] == "started_on":
1727 ordering_params = "created"
1728
1729 request.GET = request.GET.copy() # get a mutable copy of the GET QueryDict
1730 request.GET['orderby'] = ":".join(ordering_params)
1731
1732 # boilerplate code that takes a request for an object type and returns a queryset
1733 # for that object type. copypasta for all needed table searches
1734 (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest)
1735 # we don't display in-progress or deleted builds
1736 queryset_all = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
1737 queryset_with_search = _get_queryset(BuildRequest, queryset_all, None, search_term, ordering_string, '-updated')
1738 queryset = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated')
1739
1740 # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
1741 build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
1742
1743 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
1744 # most recent build is like projects' most recent builds, but across all projects
1745 build_mru = BuildRequest.objects.all()
1746 build_mru = list(build_mru.filter(Q(state__lt=BuildRequest.REQ_COMPLETED) or Q(state=BuildRequest.REQ_DELETED)).order_by("-pk")) + list(build_mru.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3])
1747
1748 fstypes_map = {};
1749 for build_request in build_info:
1750 # set display variables for build request
1751 build_request.machine = build_request.brvariable_set.get(name="MACHINE").value
1752 build_request.timespent = build_request.updated - build_request.created
1753
1754 # set up list of fstypes for each build
1755 if build_request.build is None:
1756 continue
1757 targets = Target.objects.filter( build_id = build_request.build.id )
1758 comma = "";
1759 extensions = "";
1760 for t in targets:
1761 if ( not t.is_image ):
1762 continue
1763 tif = Target_Image_File.objects.filter( target_id = t.id )
1764 for i in tif:
1765 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
1766 if s == i.file_name:
1767 s=re.sub('.*\.', '', i.file_name)
1768 if None == re.search(s,extensions):
1769 extensions += comma + s
1770 comma = ", "
1771 fstypes_map[build_request.build.id]=extensions
1772
1773
1774 # send the data to the template
1775 context = {
1776 # specific info for
1777 'mru' : build_mru,
1778 # TODO: common objects for all table views, adapt as needed
1779 'objects' : build_info,
1780 'objectname' : "builds",
1781 'default_orderby' : 'updated:-',
1782 'fstypes' : fstypes_map,
1783 'search_term' : search_term,
1784 'total_count' : queryset_with_search.count(),
1785 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
1786 'tablecols' : [
1787 {'name': 'Outcome', # column with a single filter
1788 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
1789 'dclass' : "span2", # indication about column width; comes from the design
1790 'orderfield': _get_toggle_order(request, "state"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
1791 'ordericon':_get_toggle_order_icon(request, "state"),
1792 # filter field will set a filter on that column with the specs in the filter description
1793 # the class field in the filter has no relation with clclass; the control different aspects of the UI
1794 # still, it is recommended for the values to be identical for easy tracking in the generated HTML
1795 'filter' : {'class' : 'outcome',
1796 'label': 'Show:',
1797 'options' : [
1798 ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_with_search.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression
1799 ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_with_search.filter(state=str(BuildRequest.REQ_FAILED)).count()),
1800 ]
1801 }
1802 },
1803 {'name': 'Target', # default column, disabled box, with just the name in the list
1804 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
1805 'orderfield': _get_toggle_order(request, "target__target"),
1806 'ordericon':_get_toggle_order_icon(request, "target__target"),
1807 },
1808 {'name': 'Machine',
1809 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
1810 'orderfield': _get_toggle_order(request, "machine"),
1811 'ordericon':_get_toggle_order_icon(request, "machine"),
1812 'dclass': 'span3'
1813 }, # a slightly wider column
1814 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
1815 'qhelp': "The date and time you started the build",
1816 'orderfield': _get_toggle_order(request, "created", True),
1817 'ordericon':_get_toggle_order_icon(request, "created"),
1818 'filter' : {'class' : 'created',
1819 'label': 'Show:',
1820 'options' : [
1821 ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=timezone.now()).count()),
1822 ("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()),
1823 ("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()),
1824 ]
1825 }
1826 },
1827 {'name': 'Completed on',
1828 'qhelp': "The date and time the build finished",
1829 'orderfield': _get_toggle_order(request, "updated", True),
1830 'ordericon':_get_toggle_order_icon(request, "updated"),
1831 'orderkey' : 'updated',
1832 'filter' : {'class' : 'updated',
1833 'label': 'Show:',
1834 'options' : [
1835 ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=timezone.now()).count()),
1836 ("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()),
1837 ("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()),
1838 ]
1839 }
1840 },
1841 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
1842 'qhelp': "How many tasks failed during the build",
1843 'filter' : {'class' : 'failed_tasks',
1844 'label': 'Show:',
1845 'options' : [
1846 ('BuildRequests with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()),
1847 ('BuildRequests without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()),
1848 ]
1849 }
1850 },
1851 {'name': 'Errors', 'clclass': 'errors_no',
1852 'qhelp': "How many errors were encountered during the build (if any)",
1853 'orderfield': _get_toggle_order(request, "errors_no", True),
1854 'ordericon':_get_toggle_order_icon(request, "errors_no"),
1855 'orderkey' : 'errors_no',
1856 'filter' : {'class' : 'errors_no',
1857 'label': 'Show:',
1858 'options' : [
1859 ('BuildRequests with errors', 'errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()),
1860 ('BuildRequests without errors', 'errors_no:0', queryset_with_search.filter(build__errors_no=0).count()),
1861 ]
1862 }
1863 },
1864 {'name': 'Warnings', 'clclass': 'warnings_no',
1865 'qhelp': "How many warnings were encountered during the build (if any)",
1866 'orderfield': _get_toggle_order(request, "build__warnings_no", True),
1867 'ordericon':_get_toggle_order_icon(request, "build__warnings_no"),
1868 'orderkey' : 'build__warnings_no',
1869 'filter' : {'class' : 'build__warnings_no',
1870 'label': 'Show:',
1871 'options' : [
1872 ('BuildRequests with warnings','build__warnings_no__gte:1', queryset_with_search.filter(build__warnings_no__gte=1).count()),
1873 ('BuildRequests without warnings','build__warnings_no:0', queryset_with_search.filter(build__warnings_no=0).count()),
1874 ]
1875 }
1876 },
1877 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
1878 'qhelp': "How long it took the build to finish",
1879 'orderfield': _get_toggle_order(request, "timespent", True),
1880 'ordericon':_get_toggle_order_icon(request, "timespent"),
1881 'orderkey' : 'timespent',
1882 },
1883 {'name': 'Image files', 'clclass': 'output',
1884 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
1885 # TODO: compute image fstypes from Target_Image_File
1886 },
1887 ]
1888 }
1889
1890 if not toastermain.settings.MANAGED:
1891 context['tablecols'].insert(-2,
1892 {'name': 'Log1',
1893 'dclass': "span4",
1894 'qhelp': "Path to the build main log file",
1895 'clclass': 'log', 'hidden': 1,
1896 'orderfield': _get_toggle_order(request, "cooker_log_path"),
1897 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
1898 'orderkey' : 'cooker_log_path',
1899 }
1900 )
1901
1902
1903 if toastermain.settings.MANAGED:
1904 context['tablecols'].append(
1905 {'name': 'Project', 'clclass': 'project',
1906 'filter': {'class': 'project',
1907 'label': 'Project:',
1908 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=BuildRequest.REQ_INPROGRESS).count()), Project.objects.all()),
1909
1910 }
1911 }
1912 )
1913
1914
1915 response = render(request, template, context)
1916 _save_parameters_cookies(response, pagesize, orderby, request)
1917 return response
1918
1919
1920
1921
1898 # new project 1922 # new project
1899 def newproject(request): 1923 def newproject(request):
1900 template = "newproject.html" 1924 template = "newproject.html"
@@ -2819,21 +2843,21 @@ if toastermain.settings.MANAGED:
2819 'filter' : {'class' : 'failed_tasks', 2843 'filter' : {'class' : 'failed_tasks',
2820 'label': 'Show:', 2844 'label': 'Show:',
2821 'options' : [ 2845 'options' : [
2822 ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()), 2846 ('Builds with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()),
2823 ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()), 2847 ('Builds without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()),
2824 ] 2848 ]
2825 } 2849 }
2826 }, 2850 },
2827 {'name': 'Errors', 'clclass': 'errors_no', 2851 {'name': 'Errors', 'clclass': 'errors_no',
2828 'qhelp': "How many errors were encountered during the build (if any)", 2852 'qhelp': "How many errors were encountered during the build (if any)",
2829 'orderfield': _get_toggle_order(request, "errors_no", True), 2853 'orderfield': _get_toggle_order(request, "build__errors_no", True),
2830 'ordericon':_get_toggle_order_icon(request, "errors_no"), 2854 'ordericon':_get_toggle_order_icon(request, "build__errors_no"),
2831 'orderkey' : 'errors_no', 2855 'orderkey' : 'build__errors_no',
2832 'filter' : {'class' : 'errors_no', 2856 'filter' : {'class' : 'build__errors_no',
2833 'label': 'Show:', 2857 'label': 'Show:',
2834 'options' : [ 2858 'options' : [
2835 ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), 2859 ('Builds with errors', 'build__errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()),
2836 ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), 2860 ('Builds without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()),
2837 ] 2861 ]
2838 } 2862 }
2839 }, 2863 },
@@ -3007,6 +3031,199 @@ else:
3007 "DEBUG" : toastermain.settings.DEBUG 3031 "DEBUG" : toastermain.settings.DEBUG
3008 } 3032 }
3009 3033
3034
3035 # shows the "all builds" page for interactive mode; this is the old code, simply moved
3036 def builds(request):
3037 template = 'build.html'
3038 # define here what parameters the view needs in the GET portion in order to
3039 # be able to display something. 'count' and 'page' are mandatory for all views
3040 # that use paginators.
3041 (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
3042 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
3043 retval = _verify_parameters( request.GET, mandatory_parameters )
3044 if retval:
3045 return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
3046
3047 # boilerplate code that takes a request for an object type and returns a queryset
3048 # for that object type. copypasta for all needed table searches
3049 (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
3050 queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS)
3051 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
3052 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
3053
3054 # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
3055 build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
3056
3057 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
3058 build_mru = Build.objects.order_by("-started_on")[:3]
3059
3060 # set up list of fstypes for each build
3061 fstypes_map = {};
3062 for build in build_info:
3063 targets = Target.objects.filter( build_id = build.id )
3064 comma = "";
3065 extensions = "";
3066 for t in targets:
3067 if ( not t.is_image ):
3068 continue
3069 tif = Target_Image_File.objects.filter( target_id = t.id )
3070 for i in tif:
3071 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
3072 if s == i.file_name:
3073 s=re.sub('.*\.', '', i.file_name)
3074 if None == re.search(s,extensions):
3075 extensions += comma + s
3076 comma = ", "
3077 fstypes_map[build.id]=extensions
3078
3079 # send the data to the template
3080 context = {
3081 # specific info for
3082 'mru' : build_mru,
3083 # TODO: common objects for all table views, adapt as needed
3084 'objects' : build_info,
3085 'objectname' : "builds",
3086 'default_orderby' : 'completed_on:-',
3087 'fstypes' : fstypes_map,
3088 'search_term' : search_term,
3089 'total_count' : queryset_with_search.count(),
3090 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
3091 'tablecols' : [
3092 {'name': 'Outcome', # column with a single filter
3093 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
3094 'dclass' : "span2", # indication about column width; comes from the design
3095 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
3096 'ordericon':_get_toggle_order_icon(request, "outcome"),
3097 # filter field will set a filter on that column with the specs in the filter description
3098 # the class field in the filter has no relation with clclass; the control different aspects of the UI
3099 # still, it is recommended for the values to be identical for easy tracking in the generated HTML
3100 'filter' : {'class' : 'outcome',
3101 'label': 'Show:',
3102 'options' : [
3103 ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression
3104 ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
3105 ]
3106 }
3107 },
3108 {'name': 'Target', # default column, disabled box, with just the name in the list
3109 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
3110 'orderfield': _get_toggle_order(request, "target__target"),
3111 'ordericon':_get_toggle_order_icon(request, "target__target"),
3112 },
3113 {'name': 'Machine',
3114 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
3115 'orderfield': _get_toggle_order(request, "machine"),
3116 'ordericon':_get_toggle_order_icon(request, "machine"),
3117 'dclass': 'span3'
3118 }, # a slightly wider column
3119 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
3120 'qhelp': "The date and time you started the build",
3121 'orderfield': _get_toggle_order(request, "started_on", True),
3122 'ordericon':_get_toggle_order_icon(request, "started_on"),
3123 'filter' : {'class' : 'started_on',
3124 'label': 'Show:',
3125 'options' : [
3126 ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()),
3127 ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()),
3128 ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()),
3129 ]
3130 }
3131 },
3132 {'name': 'Completed on',
3133 'qhelp': "The date and time the build finished",
3134 'orderfield': _get_toggle_order(request, "completed_on", True),
3135 'ordericon':_get_toggle_order_icon(request, "completed_on"),
3136 'orderkey' : 'completed_on',
3137 'filter' : {'class' : 'completed_on',
3138 'label': 'Show:',
3139 'options' : [
3140 ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()),
3141 ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).count()),
3142 ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()),
3143 ]
3144 }
3145 },
3146 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
3147 'qhelp': "How many tasks failed during the build",
3148 'filter' : {'class' : 'failed_tasks',
3149 'label': 'Show:',
3150 'options' : [
3151 ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
3152 ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
3153 ]
3154 }
3155 },
3156 {'name': 'Errors', 'clclass': 'errors_no',
3157 'qhelp': "How many errors were encountered during the build (if any)",
3158 'orderfield': _get_toggle_order(request, "errors_no", True),
3159 'ordericon':_get_toggle_order_icon(request, "errors_no"),
3160 'orderkey' : 'errors_no',
3161 'filter' : {'class' : 'errors_no',
3162 'label': 'Show:',
3163 'options' : [
3164 ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
3165 ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
3166 ]
3167 }
3168 },
3169 {'name': 'Warnings', 'clclass': 'warnings_no',
3170 'qhelp': "How many warnings were encountered during the build (if any)",
3171 'orderfield': _get_toggle_order(request, "warnings_no", True),
3172 'ordericon':_get_toggle_order_icon(request, "warnings_no"),
3173 'orderkey' : 'warnings_no',
3174 'filter' : {'class' : 'warnings_no',
3175 'label': 'Show:',
3176 'options' : [
3177 ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
3178 ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
3179 ]
3180 }
3181 },
3182 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
3183 'qhelp': "How long it took the build to finish",
3184 'orderfield': _get_toggle_order(request, "timespent", True),
3185 'ordericon':_get_toggle_order_icon(request, "timespent"),
3186 'orderkey' : 'timespent',
3187 },
3188 {'name': 'Image files', 'clclass': 'output',
3189 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
3190 # TODO: compute image fstypes from Target_Image_File
3191 },
3192 ]
3193 }
3194
3195 if not toastermain.settings.MANAGED:
3196 context['tablecols'].insert(-2,
3197 {'name': 'Log1',
3198 'dclass': "span4",
3199 'qhelp': "Path to the build main log file",
3200 'clclass': 'log', 'hidden': 1,
3201 'orderfield': _get_toggle_order(request, "cooker_log_path"),
3202 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
3203 'orderkey' : 'cooker_log_path',
3204 }
3205 )
3206
3207
3208 if toastermain.settings.MANAGED:
3209 context['tablecols'].append(
3210 {'name': 'Project', 'clclass': 'project',
3211 'filter': {'class': 'project',
3212 'label': 'Project:',
3213 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()),
3214
3215 }
3216 }
3217 )
3218
3219
3220 response = render(request, template, context)
3221 _save_parameters_cookies(response, pagesize, orderby, request)
3222 return response
3223
3224
3225
3226
3010 def newproject(request): 3227 def newproject(request):
3011 raise Exception("page not available in interactive mode") 3228 raise Exception("page not available in interactive mode")
3012 3229