bitbake: toaster: Build dashboard implementation

This patch adds the build dashboard page implementation,
which is the landing page for the Toaster GUI.

Also adds correct links from the main build page
to the various parts of the dashboard.

    [YOCTO #4258]

(Bitbake rev: bf7fbf5c0ee39564d813f82e194242f9d4f73c47)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Alexandru DAMIAN 2013-12-13 17:14:34 +00:00 committed by Richard Purdie
parent 2251426ae4
commit 5482409a37
6 changed files with 125 additions and 23 deletions

View File

@ -3,7 +3,7 @@
{% block pagecontent %}
<div class="span12">
<div class="">
<!-- Breadcrumbs -->
<div class="section">
<ul class="breadcrumb" id="breadcrumb">
@ -40,12 +40,11 @@
<li><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li>
</ul>
</div>
<!-- end left sidebar container -->
<!-- Begin right container -->
<div class="row span10">
{% block buildinfomain %}{% endblock %}
</div>
<!-- End right container -->
</div>

View File

@ -53,8 +53,8 @@
$('.progress, .lead span').tooltip({container:'table', placement:'top'});
$(".pagesize").change(function () {
$(".pagesize option:selected").each(function ()
{reload_params({"count":$(this).text()}); });
console.log("page size change");
reload_params({"count":$(this).val()}); ;
});
});
</script>

View File

@ -12,7 +12,6 @@
Recent Builds
</h1>
</div>
{{build_mru}}
{% for build in mru %}
<div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}">
<div class="row-fluid">
@ -74,17 +73,17 @@
</tr>
{% for build in objects %}
<tr class="data">
<td class="outcome"><a href="{% url "configuration" 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>
<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>
<td class="target">{% for t in build.target_set.all %}{%if t.is_image %}<a href="{% url "target" build.id t.id %}">{% endif %}{{t.target}}{% if t.is_image %}</a>{% endif %}<br/>{% endfor %}</td>
<td class="machine">{{build.machine}}</td>
<td class="started_on">{{build.started_on}}</td>
<td class="completed_on">{{build.completed_on}}</td>
<td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
<td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on}}</a></td>
<td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on}}</a></td>
<td class="failed_tasks"></td>
<td class="errors">{% if build.errors_no %}<a class="error" href="#">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td>
<td class="warnings">{% if build.warnings_no %}<a class="warning" href="#">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
<td class="time">{{build|timespent}}</td>
<td class="errors">{% if build.errors_no %}<a class="error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td>
<td class="warnings">{% if build.warnings_no %}<a class="warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
<td class="time"><a href="{% url "buildtime" build.id %}">{{build|timespent}}</a></td>
<td class="log">{{build.log}}</td>
<td class="output">{% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}{{build.image_fstypes}}{% endif %}{% endfor %}{% endif %}</td>
<td class="output">{% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}<a href="{%url "builddashboard" build.id%}#images">{{build.image_fstypes}}</a>{% endif %}{% endfor %}{% endif %}</td>
</tr>
{% endfor %}

View File

@ -1,8 +1,74 @@
{% extends "basebuildpage.html" %}
{% load humanize %}
{% load projecttags %}
{% block localbreadcrumb %}
<li>Dashboard</li>
{% endblock %}
{% block buildinfomain %}
<!-- page title -->
<div class="row-fluid span10">
<div class="page-header">
<h1>{{build.target_set.all|join:" "}} {{build.machine}}</h1>
</div>
</div>
<!-- build result bar -->
<div class="row-fluid span10 pull-right">
<div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}">
<div class="row-fluid lead">
<span class="pull-left"><strong>{%if build.outcome == build.SUCCEEDED%}Completed{%elif build.outcome == build.FAILED%}Failed{%else%}{%endif%}</strong> {{build.completed_on|naturaltime}} with </span>{%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}{% if build.errors_no %}
<span class="span2"><i class="icon-minus-sign red"></i><strong><a href="{%url 'builddashboard' build.pk%}" class="error"> {{build.errors_no}} error{{build.errors_no|pluralize}}</a></strong></span>
{% endif %}
{% if build.warnings_no %}
<span class="span2"><i class="icon-warning-sign yellow"></i><strong><a href="{%url 'builddashboard' build.pk%}" class="warning"> {{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a></strong></span>
{% endif %}
<span class="pull-right">Build time: <a href="build-time.html">{{ build|timespent }}</a></span>
{%endif%}
</div>
</div>
</div>
{%if build.outcome == build.SUCCEEDED%}
<!-- built images -->
<div class="row-fluid span10 pull-right">
<h2>Images</h2>
<div class="well" style="background-color:transparent;">
</div>
</div>
{%else%}
<!-- error dump -->
{%endif%}
<!-- build summary -->
<div class="row-fluid span10 pull-right">
<h2>Build summary</h2>
<div class="well span4" style="margin-left:0px; background-color:transparent;">
<h4><a href="{%url 'configuration' build.pk%}">Configuration</a></h4>
<dl>
<dt>Machine</dt><dd>{{build.machine}}</dd>
<dt>Distro</dt><dd></dd>
<dt>Layers</dt>{% for i in build.layer_version_build.all %}<dd>{{i.layer.name}}</dd>{%endfor%}
</dl>
</div>
<div class="well span4" style="background-color:transparent;">
<h4><a href="{%url 'tasks' build.pk%}">Tasks</a></h4>
<dl>
<dt>Total number of tasks</dt><dd>{{build.task_build.all.count}}</dd>
<dt>Tasks executed</dt><dd>{% query build.task_build task_executed=1 order__gt=0 as exectask%}{{exectask.count}}</dd>
<dt>Tasks prebuilt</dt><dd>{% query build.task_build task_executed=0 order__gt=0 as noexectask%}{{noexectask.count}}</dd>
<dt>Reuse</dt><dd>{% query build.task_build order__gt=0 as texec %}{{noexectask.count|multiply:100|divide:texec.count}}%</dd>
</dl>
</div>
<div class="well span4" style="background-color:transparent;">
<h4><a href="{% url 'recipes' build.pk %}">Recipes</a> & <a href="{% url 'packages' build.pk %}">Packages</a></h4>
<dl>
<dt>Recipes used</dt><dd>{{recipecount}}</dd>
<dt>Packages built</dt><dd>{{build.package_set.all.count}}</dd>
</dl>
</div>
</div>
{% endblock %}

View File

@ -29,3 +29,21 @@ def time_difference(start_time, end_time):
def timespent(build_object):
tdsec = (build_object.completed_on - build_object.started_on).total_seconds()
return "%02d:%02d:%02d" % (int(tdsec/3600), int((tdsec - tdsec/ 3600)/ 60), int(tdsec) % 60)
@register.assignment_tag
def query(qs, **kwargs):
""" template tag which allows queryset filtering. Usage:
{% query books author=author as mybooks %}
{% for book in mybooks %}
...
{% endfor %}
"""
return qs.filter(**kwargs)
@register.filter
def divide(value, arg):
return int(value) / int(arg)
@register.filter
def multiply(value, arg):
return int(value) * int(arg)

View File

@ -59,10 +59,10 @@ def _verify_parameters(g, mandatory_parameters):
return miss
return None
def _redirect_parameters(view, g, mandatory_parameters):
def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
import urllib
from django.core.urlresolvers import reverse
url = reverse(view)
url = reverse(view, kwargs=kwargs)
params = {}
for i in g:
params[i] = g[i]
@ -70,7 +70,7 @@ def _redirect_parameters(view, g, mandatory_parameters):
if not i in params:
params[i] = mandatory_parameters[i]
return redirect(url + "?%s" % urllib.urlencode(params))
return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs)
# shows the "all builds" page
@ -82,7 +82,7 @@ def builds(request):
mandatory_parameters = { 'count': 10, 'page' : 1};
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( builds, request.GET, mandatory_parameters)
return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
# retrieve the objects that will be displayed in the table
build_info = _build_page_range(Paginator(Build.objects.exclude(outcome = Build.IN_PROGRESS).order_by("-id"), request.GET.get('count', 10)),request.GET.get('page', 1))
@ -125,6 +125,7 @@ def builddashboard(request, build_id):
return redirect(builds)
context = {
'build' : Build.objects.filter(pk=build_id)[0],
'recipecount' : Recipe.objects.filter(layer_version__id__in=Layer_Version.objects.filter(build=build_id)).count()
}
return render(request, template, context)
@ -186,8 +187,12 @@ def _find_task_provider(task):
def tasks(request, build_id):
template = 'task.html'
mandatory_parameters = { 'count': 100, 'page' : 1};
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( 'tasks', request.GET, mandatory_parameters, build_id = build_id)
tasks = _build_page_range(Paginator(Task.objects.filter(build=build_id), 100),request.GET.get('page', 1))
tasks = _build_page_range(Paginator(Task.objects.filter(build=build_id, order__gt=0), request.GET.get('count', 100)),request.GET.get('page', 1))
for t in tasks:
if t.outcome == Task.OUTCOME_COVERED:
@ -199,16 +204,25 @@ def tasks(request, build_id):
def recipes(request, build_id):
template = 'recipe.html'
mandatory_parameters = { 'count': 100, 'page' : 1};
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( 'recipes', request.GET, mandatory_parameters, build_id = build_id)
recipes = _build_page_range(Paginator(Recipe.objects.filter(build_recipe=build_id), 100),request.GET.get('page', 1))
recipes = _build_page_range(Paginator(Recipe.objects.filter(layer_version__id__in=Layer_Version.objects.filter(build=build_id)), request.GET.get('count', 100)),request.GET.get('page', 1))
context = {'build': Build.objects.filter(pk=build_id)[0], 'objects': recipes}
context = {'build': Build.objects.filter(pk=build_id)[0], 'objects': recipes, }
return render(request, template, context)
def configuration(request, build_id):
template = 'configuration.html'
mandatory_parameters = { 'count': 100, 'page' : 1};
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( 'configuration', request.GET, mandatory_parameters, build_id = build_id)
variables = _build_page_range(Paginator(Variable.objects.filter(build=build_id), 50), request.GET.get('page', 1))
context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : variables}
return render(request, template, context)
@ -245,7 +259,13 @@ def diskio(request, build_id):
def bpackage(request, build_id):
template = 'bpackage.html'
packages = Package.objects.filter(build = build_id)
mandatory_parameters = { 'count': 100, 'page' : 1};
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( 'packages', request.GET, mandatory_parameters, build_id = build_id)
packages = _build_page_range(Paginator(Package.objects.filter(build = build_id), request.GET.get('count', 100)),request.GET.get('page', 1))
context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : packages}
return render(request, template, context)