diff options
Diffstat (limited to 'bitbake/lib/toaster/toastergui/views.py')
-rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 615 |
1 files changed, 416 insertions, 199 deletions
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 | ||