summaryrefslogtreecommitdiffstats
path: root/bitbake
diff options
context:
space:
mode:
authorAlexandru DAMIAN <alexandru.damian@intel.com>2015-01-16 16:42:40 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-01-21 14:37:39 +0000
commitd1dbf4c078e04fdc0f3b971f62cdefcba4904232 (patch)
treecd77abd7c289e1ea1bd996d3949aaf91671898b1 /bitbake
parent9a51fb39dbde61baae5bf9094bbb1924bcfffd0a (diff)
downloadpoky-d1dbf4c078e04fdc0f3b971f62cdefcba4904232.tar.gz
bitbake: toaster: project builds page
This is a complete re-write of the "Project builds" page based on the "All builds" page in managed mode. [YOCTO #6589] (Bitbake rev: 0353d49ae934c4595408e1b7a1443769f095f2aa) Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake')
-rw-r--r--bitbake/lib/toaster/bldcontrol/models.py2
-rw-r--r--bitbake/lib/toaster/toastergui/templates/projectbuilds.html118
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py270
3 files changed, 160 insertions, 230 deletions
diff --git a/bitbake/lib/toaster/bldcontrol/models.py b/bitbake/lib/toaster/bldcontrol/models.py
index cab4463647..2386d2345a 100644
--- a/bitbake/lib/toaster/bldcontrol/models.py
+++ b/bitbake/lib/toaster/bldcontrol/models.py
@@ -104,6 +104,8 @@ class BuildRequest(models.Model):
104 (REQ_DELETED, "deleted"), 104 (REQ_DELETED, "deleted"),
105 ) 105 )
106 106
107 search_allowed_fields = ("brtarget__target",)
108
107 project = models.ForeignKey(Project) 109 project = models.ForeignKey(Project)
108 build = models.OneToOneField(Build, null = True) # TODO: toasterui should set this when Build is created 110 build = models.OneToOneField(Build, null = True) # TODO: toasterui should set this when Build is created
109 environment = models.ForeignKey(BuildEnvironment, null = True) 111 environment = models.ForeignKey(BuildEnvironment, null = True)
diff --git a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
index 8c5942c7cb..8f9172c6d5 100644
--- a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
+++ b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
@@ -9,40 +9,72 @@
9{% block projectinfomain %} 9{% block projectinfomain %}
10 <div class="page-header"> 10 <div class="page-header">
11 <h1> 11 <h1>
12 All builds 12 {% if objects.paginator.count == 0 %}
13 <i class="icon-question-sign get-help heading-help" title="This page lists all the layers compatible with Yocto Project 1.7 'Dxxxx' that Toaster knows about. They include community-created layers suitable for use on top of OpenEmbedded Core and any layers you have imported"></i> 13 No builds found
14 </h1> 14
15 </div> 15 {% else %}
16 <!--div class="alert"> 16 {% if request.GET.filter or request.GET.search %}
17 <div class="input-append" style="margin-bottom:0px;"> 17 {{objects.paginator.count}} builds found
18 <input class="input-xxlarge" type="text" placeholder="Search layers" value="browser" /> 18 {% else %}
19 <a class="add-on btn"> 19 Project builds <small>({{objects.paginator.count}})</small>
20 <i class="icon-remove"></i> 20 {% endif %}
21 </a> 21 {% endif %}
22 <button class="btn" type="button">Search</button> 22 <i class="icon-question-sign get-help heading-help" title="This page lists all the builds for the current project"></i>
23 <a class="btn btn-link" href="#">Show all layers</a> 23 </h1>
24 </div>
25 </div-->
26 <div id="layer-added" class="alert alert-info lead" style="display:none;"></div>
27 <div id="layer-removed" class="alert alert-info lead" style="display:none;">
28 <button type="button" class="close" data-dismiss="alert">&times;</button>
29 <strong>1</strong> layer deleted from <a href="project-with-targets.html">your project</a>: <a href="#">meta-aarch64</a>
30 </div> 24 </div>
31 25
32 26
33{% include "basetable_top.html" %} 27 {% if objects.paginator.count == 0 %}
34 {% for build in objects %} 28 <div class="row-fluid">
29 <div class="alert">
30 <form class="no-results input-append" id="searchform">
31 <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 %}
32 <button class="btn" type="submit" value="Search">Search</button>
33 <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all builds</button>
34 </form>
35 </div>
36 </div>
37
38
39 {% else %}
40
41 {% include "basetable_top.html" %}
42 <!-- 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 -->
43 {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #}
35 <tr class="data"> 44 <tr class="data">
36 <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> 45 <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>
37 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 46 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
38 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 47 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
39 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 48 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
40 <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td> 49 <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td>
41 <td class="failed_tasks error">{% query build.task_build outcome=4 order__gt=0 as exectask%}{% if exectask.count == 1 %}<a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a>{% elif exectask.count > 1%}<a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}}</a>{%endif%}</td> 50 <td class="failed_tasks error">
42 <td class="errors_no">{% if build.errors_no %}<a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td> 51 {% query build.task_build outcome=4 order__gt=0 as exectask%}
52 {% if exectask.count == 1 %}
53 <a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a>
54 {% if MANAGED and build.project %}
55 <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}">
56 <i class="icon-download-alt" title="" data-original-title="Download task log file"></i>
57 </a>
58 {% endif %}
59 {% elif exectask.count > 1%}
60 <a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}} task{{exectask.count|pluralize}}</a>
61 {%endif%}
62 </td>
63 <td class="errors_no">
64 {% if build.errors_no %}
65 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
66 {% if MANAGED and build.project %}
67 <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
68 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
69 </a>
70 {% endif %}
71 {%endif%}
72 </td>
43 <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> 73 <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>
44 <td class="time"><a href="{% url "buildtime" build.id %}">{{build.timespent|sectohms}}</a></td> 74 <td class="time"><a href="{% url "buildtime" build.id %}">{{build.timespent|sectohms}}</a></td>
45 <td class="log">{{build.cooker_log_path}}</td> 75 {% if not MANAGED or not build.project %}
76 <td class="log">{{build.cooker_log_path}}</td>
77 {% endif %}
46 <td class="output"> 78 <td class="output">
47 {% if build.outcome == build.SUCCEEDED %} 79 {% if build.outcome == build.SUCCEEDED %}
48 <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a> 80 <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
@@ -50,10 +82,44 @@
50 </td> 82 </td>
51 </tr> 83 </tr>
52 84
53 {% endfor %}
54{% include "basetable_bottom.html" %}
55 85
56 <!-- Modals --> 86 {%endwith%}
87 {% else %} {# we don't have a build for this build request, mask the data with build request data #}
88
89
90
91 <tr class="data">
92 <td class="outcome">{% if buildrequest.state == buildrequest.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td>
93 <td class="target">
94 <span data-toggle="tooltip" {%if br.brtarget_set.all.count > 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
95 </td>
96 <td class="machine">
97 {{br.machine}}
98 </td>
99 <td class="started_on">
100 {{br.created|date:"d/m/y H:i"}}
101 </td>
102 <td class="completed_on">
103 {{br.updated|date:"d/m/y H:i"}}
104 </td>
105 <td class="failed_tasks error">
106 {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}}
107 </td>
108 <td class="errors_no">
109 </td>
110 <td class="warnings_no">
111 </td>
112 <td class="time">
113 {{br.timespent.total_seconds|sectohms}}
114 </td>
115 <td class="output"> {# we have no output here #}
116 </td>
117 </tr>
118 {%endif%}
119 {% endfor %}
120
57 121
122 {% include "basetable_bottom.html" %}
123{% endif %}
58 124
59{% endblock %} 125{% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index e8e4927b7e..4fae70b48b 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -1705,6 +1705,11 @@ if toastermain.settings.MANAGED:
1705 return ret 1705 return ret
1706 1706
1707 1707
1708 class InvalidRequestException(Exception):
1709 def __init__(self, response):
1710 self.response = response
1711
1712
1708 # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds 1713 # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds
1709 def builds(request): 1714 def builds(request):
1710 template = 'managed_builds.html' 1715 template = 'managed_builds.html'
@@ -1712,19 +1717,53 @@ if toastermain.settings.MANAGED:
1712 # be able to display something. 'count' and 'page' are mandatory for all views 1717 # be able to display something. 'count' and 'page' are mandatory for all views
1713 # that use paginators. 1718 # that use paginators.
1714 1719
1720 buildrequests = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
1721
1722 try:
1723 context, pagesize, orderby = _build_list_helper(request, buildrequests)
1724 except InvalidRequestException as e:
1725 return _redirect_parameters( builds, request.GET, e.response)
1726
1727 context['tablecols'].append(
1728 {'name': 'Project', 'clclass': 'project',
1729 'filter': {'class': 'project',
1730 'label': 'Project:',
1731 'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=BuildRequest.REQ_INPROGRESS).count()), Project.objects.all()),
1732
1733 }
1734 }
1735 )
1736
1737 response = render(request, template, context)
1738 _save_parameters_cookies(response, pagesize, orderby, request)
1739 return response
1740
1741
1742
1743 # helper function, to be used on "all builds" and "project builds" pages
1744 def _build_list_helper(request, buildrequests):
1715 # ATTN: we use here the ordering parameters for interactive mode; the translation for BuildRequest fields will happen below 1745 # 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:-') 1746 (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
1717 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } 1747 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1718 retval = _verify_parameters( request.GET, mandatory_parameters ) 1748 retval = _verify_parameters( request.GET, mandatory_parameters )
1719 if retval: 1749 if retval:
1720 return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters) 1750 raise InvalidRequestException(mandatory_parameters)
1721 1751
1752 orig_orderby = orderby
1722 # translate interactive mode ordering to managed mode ordering 1753 # translate interactive mode ordering to managed mode ordering
1723 ordering_params = orderby.split(":") 1754 ordering_params = orderby.split(":")
1724 if ordering_params[0] == "completed_on": 1755 if ordering_params[0] == "completed_on":
1725 ordering_params[0] = "updated" 1756 ordering_params[0] = "updated"
1726 if ordering_params[0] == "started_on": 1757 if ordering_params[0] == "started_on":
1727 ordering_params = "created" 1758 ordering_params[0] = "created"
1759 if ordering_params[0] == "errors_no":
1760 ordering_params[0] = "build__errors_no"
1761 if ordering_params[0] == "warnings_no":
1762 ordering_params[0] = "build__warnings_no"
1763 if ordering_params[0] == "machine":
1764 ordering_params[0] = "build__machine"
1765 if ordering_params[0] == "target__target":
1766 ordering_params[0] = "brtarget__target"
1728 1767
1729 request.GET = request.GET.copy() # get a mutable copy of the GET QueryDict 1768 request.GET = request.GET.copy() # get a mutable copy of the GET QueryDict
1730 request.GET['orderby'] = ":".join(ordering_params) 1769 request.GET['orderby'] = ":".join(ordering_params)
@@ -1733,7 +1772,7 @@ if toastermain.settings.MANAGED:
1733 # for that object type. copypasta for all needed table searches 1772 # for that object type. copypasta for all needed table searches
1734 (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest) 1773 (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest)
1735 # we don't display in-progress or deleted builds 1774 # 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) 1775 queryset_all = buildrequests
1737 queryset_with_search = _get_queryset(BuildRequest, queryset_all, None, search_term, ordering_string, '-updated') 1776 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') 1777 queryset = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated')
1739 1778
@@ -1802,13 +1841,13 @@ if toastermain.settings.MANAGED:
1802 }, 1841 },
1803 {'name': 'Target', # default column, disabled box, with just the name in the list 1842 {'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)", 1843 '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"), 1844 'orderfield': _get_toggle_order(request, "brtarget__target"),
1806 'ordericon':_get_toggle_order_icon(request, "target__target"), 1845 'ordericon':_get_toggle_order_icon(request, "brtarget__target"),
1807 }, 1846 },
1808 {'name': 'Machine', 1847 {'name': 'Machine',
1809 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe", 1848 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
1810 'orderfield': _get_toggle_order(request, "machine"), 1849 'orderfield': _get_toggle_order(request, "build__machine"),
1811 'ordericon':_get_toggle_order_icon(request, "machine"), 1850 'ordericon':_get_toggle_order_icon(request, "build__machine"),
1812 'dclass': 'span3' 1851 'dclass': 'span3'
1813 }, # a slightly wider column 1852 }, # a slightly wider column
1814 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column 1853 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
@@ -1843,21 +1882,21 @@ if toastermain.settings.MANAGED:
1843 'filter' : {'class' : 'failed_tasks', 1882 'filter' : {'class' : 'failed_tasks',
1844 'label': 'Show:', 1883 'label': 'Show:',
1845 'options' : [ 1884 'options' : [
1846 ('BuildRequests with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()), 1885 ('Build 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()), 1886 ('Build without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()),
1848 ] 1887 ]
1849 } 1888 }
1850 }, 1889 },
1851 {'name': 'Errors', 'clclass': 'errors_no', 1890 {'name': 'Errors', 'clclass': 'errors_no',
1852 'qhelp': "How many errors were encountered during the build (if any)", 1891 'qhelp': "How many errors were encountered during the build (if any)",
1853 'orderfield': _get_toggle_order(request, "errors_no", True), 1892 'orderfield': _get_toggle_order(request, "build__errors_no", True),
1854 'ordericon':_get_toggle_order_icon(request, "errors_no"), 1893 'ordericon':_get_toggle_order_icon(request, "build__errors_no"),
1855 'orderkey' : 'errors_no', 1894 'orderkey' : 'errors_no',
1856 'filter' : {'class' : 'errors_no', 1895 'filter' : {'class' : 'errors_no',
1857 'label': 'Show:', 1896 'label': 'Show:',
1858 'options' : [ 1897 'options' : [
1859 ('BuildRequests with errors', 'errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()), 1898 ('Build with errors', 'build__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()), 1899 ('Build without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()),
1861 ] 1900 ]
1862 } 1901 }
1863 }, 1902 },
@@ -1869,15 +1908,15 @@ if toastermain.settings.MANAGED:
1869 'filter' : {'class' : 'build__warnings_no', 1908 'filter' : {'class' : 'build__warnings_no',
1870 'label': 'Show:', 1909 'label': 'Show:',
1871 'options' : [ 1910 'options' : [
1872 ('BuildRequests with warnings','build__warnings_no__gte:1', queryset_with_search.filter(build__warnings_no__gte=1).count()), 1911 ('Build 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()), 1912 ('Build without warnings','build__warnings_no:0', queryset_with_search.filter(build__warnings_no=0).count()),
1874 ] 1913 ]
1875 } 1914 }
1876 }, 1915 },
1877 {'name': 'Time', 'clclass': 'time', 'hidden' : 1, 1916 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
1878 'qhelp': "How long it took the build to finish", 1917 'qhelp': "How long it took the build to finish",
1879 'orderfield': _get_toggle_order(request, "timespent", True), 1918# 'orderfield': _get_toggle_order(request, "timespent", True),
1880 'ordericon':_get_toggle_order_icon(request, "timespent"), 1919# 'ordericon':_get_toggle_order_icon(request, "timespent"),
1881 'orderkey' : 'timespent', 1920 'orderkey' : 'timespent',
1882 }, 1921 },
1883 {'name': 'Image files', 'clclass': 'output', 1922 {'name': 'Image files', 'clclass': 'output',
@@ -1886,38 +1925,7 @@ if toastermain.settings.MANAGED:
1886 }, 1925 },
1887 ] 1926 ]
1888 } 1927 }
1889 1928 return context, pagesize, orderby
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 1929
1922 # new project 1930 # new project
1923 def newproject(request): 1931 def newproject(request):
@@ -1957,7 +1965,7 @@ if toastermain.settings.MANAGED:
1957 prj = Project.objects.create_project(name = request.POST['projectname'], release = Release.objects.get(pk = request.POST['projectversion'])) 1965 prj = Project.objects.create_project(name = request.POST['projectname'], release = Release.objects.get(pk = request.POST['projectversion']))
1958 prj.user_id = request.user.pk 1966 prj.user_id = request.user.pk
1959 prj.save() 1967 prj.save()
1960 return redirect(reverse(project, args = (prj.pk,)) + "#/newproject") 1968 return redirect(reverse(project, args=(prj.pk,)) + "#/newproject")
1961 1969
1962 except (IntegrityError, BadParameterException) as e: 1970 except (IntegrityError, BadParameterException) as e:
1963 # fill in page with previously submitted values 1971 # fill in page with previously submitted values
@@ -2738,163 +2746,17 @@ if toastermain.settings.MANAGED:
2738 2746
2739 def projectbuilds(request, pid): 2747 def projectbuilds(request, pid):
2740 template = 'projectbuilds.html' 2748 template = 'projectbuilds.html'
2741 # define here what parameters the view needs in the GET portion in order to 2749 buildrequests = BuildRequest.objects.exclude(project_id = pid, state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
2742 # be able to display something. 'count' and 'page' are mandatory for all views
2743 # that use paginators.
2744 mandatory_parameters = { 'count': 10, 'page' : 1, 'orderby' : 'completed_on:-' };
2745 retval = _verify_parameters( request.GET, mandatory_parameters )
2746
2747 # boilerplate code that takes a request for an object type and returns a queryset
2748 # for that object type. copypasta for all needed table searches
2749 (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
2750 queryset_all = Build.objects.all().exclude(outcome = Build.IN_PROGRESS)
2751 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
2752 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
2753 2750
2754 # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display 2751 try:
2755 build_info = _build_page_range(Paginator(queryset, request.GET.get('count', 10)),request.GET.get('page', 1)) 2752 context, pagesize, orderby = _build_list_helper(request, buildrequests)
2756 2753 except InvalidRequestException as e:
2757 2754 return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid)
2758 # set up list of fstypes for each build
2759 fstypes_map = {};
2760 for build in build_info:
2761 targets = Target.objects.filter( build_id = build.id )
2762 comma = "";
2763 extensions = "";
2764 for t in targets:
2765 if ( not t.is_image ):
2766 continue
2767 tif = Target_Image_File.objects.filter( target_id = t.id )
2768 for i in tif:
2769 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
2770 if s == i.file_name:
2771 s=re.sub('.*\.', '', i.file_name)
2772 if None == re.search(s,extensions):
2773 extensions += comma + s
2774 comma = ", "
2775 fstypes_map[build.id]=extensions
2776 2755
2777 # send the data to the template 2756 response = render(request, template, context)
2778 context = { 2757 _save_parameters_cookies(response, pagesize, orderby, request)
2779 'objects' : build_info,
2780 'objectname' : "builds",
2781 'default_orderby' : 'completed_on:-',
2782 'fstypes' : fstypes_map,
2783 'search_term' : search_term,
2784 'total_count' : queryset_with_search.count(),
2785 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
2786 'tablecols' : [
2787 {'name': 'Outcome', # column with a single filter
2788 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
2789 'dclass' : "span2", # indication about column width; comes from the design
2790 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
2791 'ordericon':_get_toggle_order_icon(request, "outcome"),
2792 # filter field will set a filter on that column with the specs in the filter description
2793 # the class field in the filter has no relation with clclass; the control different aspects of the UI
2794 # still, it is recommended for the values to be identical for easy tracking in the generated HTML
2795 'filter' : {'class' : 'outcome',
2796 'label': 'Show:',
2797 'options' : [
2798 ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression
2799 ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
2800 ]
2801 }
2802 },
2803 {'name': 'Target', # default column, disabled box, with just the name in the list
2804 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
2805 'orderfield': _get_toggle_order(request, "target__target"),
2806 'ordericon':_get_toggle_order_icon(request, "target__target"),
2807 },
2808 {'name': 'Machine',
2809 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
2810 'orderfield': _get_toggle_order(request, "machine"),
2811 'ordericon':_get_toggle_order_icon(request, "machine"),
2812 'dclass': 'span3'
2813 }, # a slightly wider column
2814 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
2815 'qhelp': "The date and time you started the build",
2816 'orderfield': _get_toggle_order(request, "started_on", True),
2817 'ordericon':_get_toggle_order_icon(request, "started_on"),
2818 'filter' : {'class' : 'started_on',
2819 'label': 'Show:',
2820 'options' : [
2821 ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()),
2822 ("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()),
2823 ("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()),
2824 ]
2825 }
2826 },
2827 {'name': 'Completed on',
2828 'qhelp': "The date and time the build finished",
2829 'orderfield': _get_toggle_order(request, "completed_on", True),
2830 'ordericon':_get_toggle_order_icon(request, "completed_on"),
2831 'orderkey' : 'completed_on',
2832 'filter' : {'class' : 'completed_on',
2833 'label': 'Show:',
2834 'options' : [
2835 ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()),
2836 ("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()),
2837 ("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()),
2838 ]
2839 }
2840 },
2841 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
2842 'qhelp': "How many tasks failed during the build",
2843 'filter' : {'class' : 'failed_tasks',
2844 'label': 'Show:',
2845 'options' : [
2846 ('Builds with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__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()),
2848 ]
2849 }
2850 },
2851 {'name': 'Errors', 'clclass': 'errors_no',
2852 'qhelp': "How many errors were encountered during the build (if any)",
2853 'orderfield': _get_toggle_order(request, "build__errors_no", True),
2854 'ordericon':_get_toggle_order_icon(request, "build__errors_no"),
2855 'orderkey' : 'build__errors_no',
2856 'filter' : {'class' : 'build__errors_no',
2857 'label': 'Show:',
2858 'options' : [
2859 ('Builds with errors', 'build__errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()),
2860 ('Builds without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()),
2861 ]
2862 }
2863 },
2864 {'name': 'Warnings', 'clclass': 'warnings_no',
2865 'qhelp': "How many warnings were encountered during the build (if any)",
2866 'orderfield': _get_toggle_order(request, "warnings_no", True),
2867 'ordericon':_get_toggle_order_icon(request, "warnings_no"),
2868 'orderkey' : 'warnings_no',
2869 'filter' : {'class' : 'warnings_no',
2870 'label': 'Show:',
2871 'options' : [
2872 ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
2873 ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
2874 ]
2875 }
2876 },
2877 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
2878 'qhelp': "How long it took the build to finish",
2879 'orderfield': _get_toggle_order(request, "timespent", True),
2880 'ordericon':_get_toggle_order_icon(request, "timespent"),
2881 'orderkey' : 'timespent',
2882 },
2883 {'name': 'Log',
2884 'dclass': "span4",
2885 'qhelp': "Path to the build main log file",
2886 'clclass': 'log', 'hidden': 1,
2887 'orderfield': _get_toggle_order(request, "cooker_log_path"),
2888 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
2889 'orderkey' : 'cooker_log_path',
2890 },
2891 {'name': 'Output', 'clclass': 'output',
2892 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
2893 },
2894 ]
2895 }
2896 2758
2897 return render(request, template, context) 2759 return response
2898 2760
2899 2761
2900 def _file_name_for_artifact(b, artifact_type, artifact_id): 2762 def _file_name_for_artifact(b, artifact_type, artifact_id):