diff options
author | David Reyna <David.Reyna@windriver.com> | 2015-02-26 21:42:00 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2015-02-27 07:35:09 +0000 |
commit | cc6ca17e80844ecb4a777276d5f5177ebbcd93f9 (patch) | |
tree | 4f528a1080fa860016ee6d3adf3fce597ebc617a | |
parent | 6b2403992f1f5f84114ec9b243813957ff907051 (diff) | |
download | bitbake-cc6ca17e80844ecb4a777276d5f5177ebbcd93f9.tar.gz |
toaster: all projects data and sorts
Implement the 'last build' data methods, enhance variable display,
add empty page and empty sort support.
[YOCTO #6682]
Signed-off-by: David Reyna <David.Reyna@windriver.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | lib/toaster/bldcontrol/models.py | 2 | ||||
-rw-r--r-- | lib/toaster/orm/models.py | 63 | ||||
-rw-r--r-- | lib/toaster/toastergui/templates/managed_builds.html | 22 | ||||
-rw-r--r-- | lib/toaster/toastergui/templates/projects.html | 60 | ||||
-rwxr-xr-x | lib/toaster/toastergui/views.py | 97 |
5 files changed, 190 insertions, 54 deletions
diff --git a/lib/toaster/bldcontrol/models.py b/lib/toaster/bldcontrol/models.py index 25d94cd3f..02cfaf708 100644 --- a/lib/toaster/bldcontrol/models.py +++ b/lib/toaster/bldcontrol/models.py @@ -106,7 +106,7 @@ class BuildRequest(models.Model): (REQ_ARCHIVE, "archive"), ) - search_allowed_fields = ("brtarget__target",) + search_allowed_fields = ("brtarget__target", "build__project__name") project = models.ForeignKey(Project) build = models.OneToOneField(Build, null = True) # TODO: toasterui should set this when Build is created diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py index 4fa9f81e4..90e11d238 100644 --- a/lib/toaster/orm/models.py +++ b/lib/toaster/orm/models.py @@ -102,6 +102,69 @@ class Project(models.Model): def __unicode__(self): return "%s (%s, %s)" % (self.name, self.release, self.bitbake_version) + def get_current_machine_name(self): + try: + return self.projectvariable_set.get(name="MACHINE").value + except (ProjectVariable.DoesNotExist,IndexError): + return( "None" ); + + def get_number_of_builds(self): + try: + return len(Build.objects.filter( project = self.id )) + except (Build.DoesNotExist,IndexError): + return( 0 ) + + def get_last_build_id(self): + try: + return Build.objects.filter( project = self.id ).order_by('-completed_on')[0].id + except (Build.DoesNotExist,IndexError): + return( -1 ) + + def get_last_outcome(self): + build_id = self.get_last_build_id + if (-1 == build_id): + return( "" ) + try: + return Build.objects.filter( id = self.get_last_build_id )[ 0 ].outcome + except (Build.DoesNotExist,IndexError): + return( "not_found" ) + + def get_last_target(self): + build_id = self.get_last_build_id + if (-1 == build_id): + return( "" ) + try: + return Target.objects.filter(build = build_id)[0].target + except (Target.DoesNotExist,IndexError): + return( "not_found" ) + + def get_last_errors(self): + build_id = self.get_last_build_id + if (-1 == build_id): + return( 0 ) + try: + return Build.objects.filter(id = build_id)[ 0 ].errors_no + except (Build.DoesNotExist,IndexError): + return( "not_found" ) + + def get_last_warnings(self): + build_id = self.get_last_build_id + if (-1 == build_id): + return( 0 ) + try: + return Build.objects.filter(id = build_id)[ 0 ].warnings_no + except (Build.DoesNotExist,IndexError): + return( "not_found" ) + + def get_last_imgfiles(self): + build_id = self.get_last_build_id + if (-1 == build_id): + return( "" ) + try: + return Variable.objects.filter(build = build_id, variable_name = "IMAGE_FSTYPES")[ 0 ].variable_value + except (Variable.DoesNotExist,IndexError): + return( "not_found" ) + # returns a queryset of compatible layers for a project def compatible_layerversions(self, release = None, layer_name = None): if release == None: diff --git a/lib/toaster/toastergui/templates/managed_builds.html b/lib/toaster/toastergui/templates/managed_builds.html index a4db55b96..e23b832ba 100644 --- a/lib/toaster/toastergui/templates/managed_builds.html +++ b/lib/toaster/toastergui/templates/managed_builds.html @@ -56,6 +56,13 @@ </td> <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> + {% if MANAGED %} + <td class="project"> + {% if build.project %} + <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a> + {% endif %} + </td> + {% endif %} <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td> <td class="failed_tasks error"> @@ -91,13 +98,6 @@ <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a> {% endif %} </td> - {% if MANAGED %} - <td class="project"> - {% if build.project %} - <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a> - {% endif %} - </td> - {% endif %} </tr> @@ -114,6 +114,11 @@ <td class="machine"> <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.machine}}</a> </td> + {% if MANAGED %} + <td class="project"> + <a href="{% url 'project' buildrequest.project.id %}">{{buildrequest.project.name}}</a> + </td> + {% endif %} <td class="started_on"> <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.created|date:"d/m/y H:i"}}</a> </td> @@ -132,9 +137,6 @@ </td> <td class="output"> {# we have no output here #} </td> - <td class="project"> - <a href="{% url 'project' buildrequest.project.id %}">{{buildrequest.project.name}}</a> - </td> </tr> {%endif%} {% endfor %} diff --git a/lib/toaster/toastergui/templates/projects.html b/lib/toaster/toastergui/templates/projects.html index 0396e25a3..88d5bd32d 100644 --- a/lib/toaster/toastergui/templates/projects.html +++ b/lib/toaster/toastergui/templates/projects.html @@ -12,25 +12,61 @@ <div class="page-header top-air"> <h1> - All projects + {% if request.GET.filter and objects.paginator.count > 0 or request.GET.search and objects.paginator.count > 0 %} + {{objects.paginator.count}} project{{objects.paginator.count|pluralize}} found + {%elif request.GET.filter and objects.paginator.count == 0 or request.GET.search and objects.paginator.count == 0 %} + No projects found + {%else%} + All projects + {%endif%} </h1> </div> -{% include "basetable_top_projectbuilds.html" %} + {% if objects.paginator.count == 0 %} + <div class="row-fluid"> + <div class="alert"> + <form class="no-results input-append" id="searchform"> + <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 %} + <button class="btn" type="submit" value="Search">Search</button> + <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all projects</button> + </form> + </div> + </div> + + {% else %} {# We have builds to display #} + {% include "basetable_top_projectbuilds.html" %} {% for o in objects %} <tr class="data"> <td><a href="{% url 'project' o.id %}">{{o.name}}</a></td> - <td><a href="{% url 'project' o.id %}">{{o.release.name}}</a></td> - <td>{{o.get_current_machine_name}}</td> - <td>{{o.get_number_of_builds}}</td> - <td class="loutcome">{{o.get_last_outcome}}</td> - <td class="ltarget">{{o.get_last_target}}</td> - <td class="lerrors">{{o.get_last_errors}}</td> - <td class="lwarnings">{{o.get_last_warnings}}</td> - <td class="limagefiles">{{o.get_last_imgfiles}}</td> - <td class="updated">{{o.updated|date:"d/m/y H:i"}}</td> + <td><a href="{% url 'project' o.id %}#project-details">{{o.release.name}}</a></td> + <td><a href="{% url 'project' o.id %}#machine-distro">{{o.get_current_machine_name}}</a></td> + {% if o.get_number_of_builds == 0 %} + <td class="muted">{{o.get_number_of_builds}}</td> + <td class="updated"></td> + <td class="loutcome"></td> + <td class="ltarget"></td> + <td class="lerrors"></td> + <td class="lwarnings"></td> + <td class="limagefiles"></td> + {% else %} + <td><a href="{% url 'projectbuilds' o.id %}">{{o.get_number_of_builds}}</a></td> + <td class="updated"><a href="{% url "builddashboard" o.get_last_build_id %}">{{o.updated|date:"d/m/y H:i"}}</a></td> + <td class="loutcome"><a href="{% url "builddashboard" o.get_last_build_id %}">{%if o.get_last_outcome == build_SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif o.get_last_outcome == build_FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> + <td class="ltarget"><a href="{% url "builddashboard" o.get_last_build_id %}">{{o.get_last_target}} </a></td> + <td class="lerrors">{% if o.get_last_errors %}<a class="errors_no error" href="{% url "builddashboard" o.get_last_build_id %}#errors">{{o.get_last_errors}} error{{o.get_last_errors|pluralize}}</a>{%endif%}</td> + <td class="lwarnings">{% if o.get_last_warnings %}<a class="warnings_no warning" href="{% url "builddashboard" o.get_last_build_id %}#warnings">{{o.get_last_warnings}} warning{{o.get_last_warnings|pluralize}}</a>{%endif%}</td> + <td class="limagefiles"> + {% if o.get_last_outcome == build_SUCCEEDED %} + <a href="{%url "builddashboard" o.get_last_build_id %}#images">{{fstypes|get_dict_value:o.id}}</a> + {% endif %} + </td> + + {% endif %} </tr> {% endfor %} -{% include "basetable_bottom.html" %} + {% include "basetable_bottom.html" %} + {% endif %} {# empty #} {% endblock %} + + diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py index a206f80b6..4f4ae67ca 100755 --- a/lib/toaster/toastergui/views.py +++ b/lib/toaster/toastergui/views.py @@ -1758,20 +1758,10 @@ if toastermain.settings.MANAGED: buildrequests = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) try: - context, pagesize, orderby = _build_list_helper(request, buildrequests) + context, pagesize, orderby = _build_list_helper(request, buildrequests, True) except InvalidRequestException as e: return _redirect_parameters( builds, request.GET, e.response) - context['tablecols'].append( - {'name': 'Project', 'clclass': 'projectx', - 'filter': {'class': 'project', - 'label': 'Project:', - 'options': map(lambda x: (x.name,'project:%d' % x.id,x.build_set.filter(outcome__lt=BuildRequest.REQ_INPROGRESS).count()), Project.objects.all()), - - } - } - ) - response = render(request, template, context) _save_parameters_cookies(response, pagesize, orderby, request) return response @@ -1779,7 +1769,7 @@ if toastermain.settings.MANAGED: # helper function, to be used on "all builds" and "project builds" pages - def _build_list_helper(request, buildrequests): + def _build_list_helper(request, buildrequests, insert_projects): # ATTN: we use here the ordering parameters for interactive mode; the translation for BuildRequest fields will happen below default_orderby = 'completed_on:-' (pagesize, orderby) = _get_parameters_values(request, 10, default_orderby) @@ -1893,6 +1883,16 @@ if toastermain.settings.MANAGED: 'ordericon':_get_toggle_order_icon(request, "build__machine"), 'dclass': 'span3' }, # a slightly wider column + ] + } + + if (insert_projects): + context['tablecols'].append( + {'name': 'Project', 'clclass': 'project', + } + ) + + context['tablecols'].append( {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column 'qhelp': "The date and time you started the build", 'orderfield': _get_toggle_order(request, "created", True), @@ -1905,7 +1905,9 @@ if toastermain.settings.MANAGED: ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), ] } - }, + } + ) + context['tablecols'].append( {'name': 'Completed on', 'qhelp': "The date and time the build finished", 'orderfield': _get_toggle_order(request, "updated", True), @@ -1919,7 +1921,9 @@ if toastermain.settings.MANAGED: ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), ] } - }, + } + ) + context['tablecols'].append( {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox 'qhelp': "How many tasks failed during the build", 'filter' : {'class' : 'failed_tasks', @@ -1931,7 +1935,9 @@ if toastermain.settings.MANAGED: queryset_all.filter(~Q(build__task_build__outcome=Task.OUTCOME_FAILED)).count()), ] } - }, + } + ) + context['tablecols'].append( {'name': 'Errors', 'clclass': 'errors_no', 'qhelp': "How many errors were encountered during the build (if any)", 'orderfield': _get_toggle_order(request, "build__errors_no", True), @@ -1946,7 +1952,9 @@ if toastermain.settings.MANAGED: queryset_all.filter(build__errors_no=0).count()), ] } - }, + } + ) + context['tablecols'].append( {'name': 'Warnings', 'clclass': 'warnings_no', 'qhelp': "How many warnings were encountered during the build (if any)", 'orderfield': _get_toggle_order(request, "build__warnings_no", True), @@ -1959,19 +1967,23 @@ if toastermain.settings.MANAGED: ('Builds without warnings','build__warnings_no:0', queryset_all.filter(build__warnings_no=0).count()), ] } - }, + } + ) + context['tablecols'].append( {'name': 'Time', 'clclass': 'time', 'hidden' : 1, 'qhelp': "How long it took the build to finish", -# 'orderfield': _get_toggle_order(request, "timespent", True), -# 'ordericon':_get_toggle_order_icon(request, "timespent"), +# 'orderfield': _get_toggle_order(request, "timespent", True), +# 'ordericon':_get_toggle_order_icon(request, "timespent"), 'orderkey' : 'timespent', - }, + } + ) + context['tablecols'].append( {'name': 'Image files', 'clclass': 'output', 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory", # TODO: compute image fstypes from Target_Image_File - }, - ] - } + } + ) + return context, pagesize, orderby # new project @@ -2898,7 +2910,7 @@ if toastermain.settings.MANAGED: buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) try: - context, pagesize, orderby = _build_list_helper(request, buildrequests) + context, pagesize, orderby = _build_list_helper(request, buildrequests, False) except InvalidRequestException as e: return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid) @@ -3019,7 +3031,7 @@ if toastermain.settings.MANAGED: def projects(request): template="projects.html" - (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:+') + (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-') mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } retval = _verify_parameters( request.GET, mandatory_parameters ) if retval: @@ -3039,7 +3051,27 @@ if toastermain.settings.MANAGED: # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) build_mru = Build.objects.order_by("-started_on")[:3] - + # translate the project's build target strings + fstypes_map = {}; + for project in project_info: + try: + targets = Target.objects.filter( build_id = project.get_last_build_id() ) + comma = ""; + extensions = ""; + for t in targets: + if ( not t.is_image ): + continue + tif = Target_Image_File.objects.filter( target_id = t.id ) + for i in tif: + s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name) + if s == i.file_name: + s=re.sub('.*\.', '', i.file_name) + if None == re.search(s,extensions): + extensions += comma + s + comma = ", " + fstypes_map[project.id]=extensions + except (Target.DoesNotExist,IndexError): + fstypes_map[project.id]=project.get_last_imgfiles context = { 'mru' : build_mru, @@ -3049,6 +3081,9 @@ if toastermain.settings.MANAGED: 'default_orderby' : 'id:-', 'search_term' : search_term, 'total_count' : queryset_with_search.count(), + 'fstypes' : fstypes_map, + 'build_FAILED' : Build.FAILED, + 'build_SUCCEEDED' : Build.SUCCEEDED, 'tablecols': [ {'name': 'Project', 'orderfield': _get_toggle_order(request, "name"), @@ -3067,6 +3102,11 @@ if toastermain.settings.MANAGED: {'name': 'Number of builds', 'qhelp': "How many builds have been run for the project", }, + {'name': 'Last build', 'clclass': 'updated', + 'orderfield': _get_toggle_order(request, "updated", True), + 'ordericon':_get_toggle_order_icon(request, "updated"), + 'orderkey' : 'updated', + }, {'name': 'Last outcome', 'clclass': 'loutcome', 'qhelp': "Tells you if the last project build completed successfully or failed", }, @@ -3082,11 +3122,6 @@ if toastermain.settings.MANAGED: {'name': 'Last image files', 'clclass': 'limagefiles', 'hidden': 1, 'qhelp': "The root file system types produced by the last project build", }, - {'name': 'Last updated', 'clclass': 'updated', - 'orderfield': _get_toggle_order(request, "updated"), - 'ordericon':_get_toggle_order_icon(request, "updated"), - 'orderkey' : 'updated', - } ] } return render(request, template, context) |