diff options
Diffstat (limited to 'bitbake')
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 | |||
138 | function _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 | |||
159 | function 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") | ||
70 | def 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 |
69 | def divide(value, arg): | 88 | def 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 | ||
260 | def 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 | ||