bitbake: toastergui: convert project builds page to ToasterTable

Use the all builds ToasterTable as the basis for the project builds
ToasterTable.

[YOCTO #8738]

(Bitbake rev: 87bcfb740dd2d9944e35a2a1f71cbf8ff3b266e9)

Signed-off-by: Elliot Smith <elliot.smith@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Elliot Smith 2016-01-15 13:00:55 +02:00 committed by Richard Purdie
parent 33b011c158
commit eaae82a19a
9 changed files with 239 additions and 49 deletions

View File

@ -7,7 +7,10 @@ function projectTopBarInit(ctx) {
var projectName = $("#project-name");
var projectNameFormToggle = $("#project-change-form-toggle");
var projectNameChangeCancel = $("#project-name-change-cancel");
// this doesn't exist for command-line builds
var newBuildTargetInput = $("#build-input");
var newBuildTargetBuildBtn = $("#build-button");
var selectedTarget;
@ -42,6 +45,12 @@ function projectTopBarInit(ctx) {
$(this).parent().removeClass('active');
});
if (!newBuildTargetInput.length) {
return;
}
/* the following only applies for non-command-line projects */
/* Recipe build input functionality */
if (ctx.numProjectLayers > 0 && ctx.machine){
newBuildTargetInput.removeAttr("disabled");

View File

@ -33,6 +33,10 @@ function tableInit(ctx){
loadData(tableParams);
// clicking on this set of elements removes the search
var clearSearchElements = $('.remove-search-btn-'+ctx.tableName +
', .show-all-'+ctx.tableName);
function loadData(tableParams){
$.ajax({
type: "GET",
@ -62,9 +66,9 @@ function tableInit(ctx){
paginationBtns.html("");
if (tableParams.search)
$('.remove-search-btn-'+ctx.tableName).show();
clearSearchElements.show();
else
$('.remove-search-btn-'+ctx.tableName).hide();
clearSearchElements.hide();
$('.table-count-' + ctx.tableName).text(tableData.total);
tableTotal = tableData.total;
@ -230,9 +234,8 @@ function tableInit(ctx){
} else {
/* Not orderable */
header.addClass("muted");
header.css("font-weight", "normal");
header.append(col.title+' ');
header.append('<span class="muted">' + col.title + '</span> ');
}
/* Setup the filter button */
@ -665,7 +668,7 @@ function tableInit(ctx){
loadData(tableParams);
});
$('.remove-search-btn-'+ctx.tableName).click(function(e){
clearSearchElements.click(function(e){
e.preventDefault();
tableParams.page = 1;

View File

@ -23,9 +23,11 @@ from toastergui.widgets import ToasterTable
from toastergui.querysetfilter import QuerysetFilter
from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
from orm.models import CustomImageRecipe, Package, Build, LogMessage, Task
from orm.models import ProjectTarget
from django.db.models import Q, Max, Count
from django.conf.urls import url
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, resolve
from django.http import HttpResponse
from django.views.generic import TemplateView
import itertools
@ -775,7 +777,7 @@ class ProjectsTable(ToasterTable):
'''
errors_template = '''
{% if data.get_number_of_builds > 0 %}
{% if data.get_number_of_builds > 0 and data.get_last_errors > 0 %}
<a class="errors.count error"
href="{% url "builddashboard" data.get_last_build_id %}#errors">
{{data.get_last_errors}} error{{data.get_last_errors | pluralize}}
@ -784,7 +786,7 @@ class ProjectsTable(ToasterTable):
'''
warnings_template = '''
{% if data.get_number_of_builds > 0 %}
{% if data.get_number_of_builds > 0 and data.get_last_warnings > 0 %}
<a class="warnings.count warning"
href="{% url "builddashboard" data.get_last_build_id %}#warnings">
{{data.get_last_warnings}} warning{{data.get_last_warnings | pluralize}}
@ -886,30 +888,45 @@ class BuildsTable(ToasterTable):
def __init__(self, *args, **kwargs):
super(BuildsTable, self).__init__(*args, **kwargs)
self.default_orderby = '-completed_on'
self.title = 'All builds'
self.static_context_extra['Build'] = Build
self.static_context_extra['Task'] = Task
# attributes that are overridden in subclasses
# title for the page
self.title = ''
# 'project' or 'all'; determines how the mrb (most recent builds)
# section is displayed
self.mrb_type = ''
def get_builds(self):
"""
overridden in ProjectBuildsTable to return builds for a
single project
"""
return Build.objects.all()
def get_context_data(self, **kwargs):
context = super(BuildsTable, self).get_context_data(**kwargs)
# for the latest builds section
queryset = Build.objects.all()
builds = self.get_builds()
finished_criteria = Q(outcome=Build.SUCCEEDED) | Q(outcome=Build.FAILED)
latest_builds = itertools.chain(
queryset.filter(outcome=Build.IN_PROGRESS).order_by("-started_on"),
queryset.filter(finished_criteria).order_by("-completed_on")[:3]
builds.filter(outcome=Build.IN_PROGRESS).order_by("-started_on"),
builds.filter(finished_criteria).order_by("-completed_on")[:3]
)
context['mru'] = list(latest_builds)
context['mrb_type'] = 'all'
context['mrb_type'] = self.mrb_type
return context
def setup_queryset(self, *args, **kwargs):
queryset = Build.objects.all()
queryset = self.get_builds()
# don't include in progress builds
queryset = queryset.exclude(outcome=Build.IN_PROGRESS)
@ -949,7 +966,8 @@ class BuildsTable(ToasterTable):
{% if data.cooker_log_path %}
&nbsp;
<a href="{% url "build_artifact" data.id "cookerlog" data.id %}">
<i class="icon-download-alt" title="Download build log"></i>
<i class="icon-download-alt get-help"
data-original-title="Download build log"></i>
</a>
{% endif %}
'''
@ -1031,19 +1049,6 @@ class BuildsTable(ToasterTable):
{% endif %}
'''
project_template = '''
{% load project_url_tag %}
<a href="{% project_url data.project %}">
{{data.project.name}}
</a>
{% if data.project.is_default %}
<i class="icon-question-sign get-help hover-help" title=""
data-original-title="This project shows information about
the builds you start from the command line while Toaster is
running" style="visibility: hidden;"></i>
{% endif %}
'''
self.add_column(title='Outcome',
help_text='Final state of the build (successful \
or failed)',
@ -1098,16 +1103,16 @@ class BuildsTable(ToasterTable):
help_text='The number of errors encountered during \
the build (if any)',
hideable=True,
orderable=False,
static_data_name='errors',
orderable=True,
static_data_name='errors_no',
static_data_template=errors_template)
self.add_column(title='Warnings',
help_text='The number of warnings encountered during \
the build (if any)',
hideable=True,
orderable=False,
static_data_name='warnings',
orderable=True,
static_data_name='warnings_no',
static_data_template=warnings_template)
self.add_column(title='Time',
@ -1125,12 +1130,6 @@ class BuildsTable(ToasterTable):
static_data_name='image_files',
static_data_template=image_files_template)
self.add_column(title='Project',
hideable=True,
orderable=False,
static_data_name='project-name',
static_data_template=project_template)
def setup_filters(self, *args, **kwargs):
# outcomes
outcome_filter = TableFilter(
@ -1239,3 +1238,122 @@ class BuildsTable(ToasterTable):
failed_tasks_filter.add_action(with_failed_tasks_action)
failed_tasks_filter.add_action(without_failed_tasks_action)
self.add_filter(failed_tasks_filter)
def post(self, request, *args, **kwargs):
""" Process HTTP POSTs which make build requests """
project = Project.objects.get(pk=kwargs['pid'])
if 'buildCancel' in request.POST:
for i in request.POST['buildCancel'].strip().split(" "):
try:
br = BuildRequest.objects.select_for_update().get(project = project, pk = i, state__lte = BuildRequest.REQ_QUEUED)
br.state = BuildRequest.REQ_DELETED
br.save()
except BuildRequest.DoesNotExist:
pass
if 'buildDelete' in request.POST:
for i in request.POST['buildDelete'].strip().split(" "):
try:
BuildRequest.objects.select_for_update().get(project = project, pk = i, state__lte = BuildRequest.REQ_DELETED).delete()
except BuildRequest.DoesNotExist:
pass
if 'targets' in request.POST:
ProjectTarget.objects.filter(project = project).delete()
s = str(request.POST['targets'])
for t in s.translate(None, ";%|\"").split(" "):
if ":" in t:
target, task = t.split(":")
else:
target = t
task = ""
ProjectTarget.objects.create(project = project,
target = target,
task = task)
project.schedule_build()
# redirect back to builds page so any new builds in progress etc.
# are visible
response = HttpResponse()
response.status_code = 302
response['Location'] = request.build_absolute_uri()
return response
class AllBuildsTable(BuildsTable):
""" Builds page for all builds """
def __init__(self, *args, **kwargs):
super(AllBuildsTable, self).__init__(*args, **kwargs)
self.title = 'All builds'
self.mrb_type = 'all'
def setup_columns(self, *args, **kwargs):
"""
All builds page shows a column for the project
"""
super(AllBuildsTable, self).setup_columns(*args, **kwargs)
project_template = '''
{% load project_url_tag %}
<a href="{% project_url data.project %}">
{{data.project.name}}
</a>
{% if data.project.is_default %}
<i class="icon-question-sign get-help hover-help" title=""
data-original-title="This project shows information about
the builds you start from the command line while Toaster is
running" style="visibility: hidden;"></i>
{% endif %}
'''
self.add_column(title='Project',
hideable=True,
orderable=True,
static_data_name='project',
static_data_template=project_template)
class ProjectBuildsTable(BuildsTable):
"""
Builds page for a single project; a BuildsTable, with the queryset
filtered by project
"""
def __init__(self, *args, **kwargs):
super(ProjectBuildsTable, self).__init__(*args, **kwargs)
self.title = 'All project builds'
self.mrb_type = 'project'
# set from the querystring
self.project_id = None
def setup_queryset(self, *args, **kwargs):
"""
NOTE: self.project_id must be set before calling super(),
as it's used in setup_queryset()
"""
self.project_id = kwargs['pid']
super(ProjectBuildsTable, self).setup_queryset(*args, **kwargs)
project = Project.objects.get(pk=self.project_id)
self.queryset = self.queryset.filter(project=project)
def get_context_data(self, **kwargs):
"""
NOTE: self.project_id must be set before calling super(),
as it's used in get_context_data()
"""
self.project_id = kwargs['pid']
context = super(ProjectBuildsTable, self).get_context_data(**kwargs)
context['project'] = Project.objects.get(pk=self.project_id)
return context
def get_builds(self):
""" override: only return builds for the relevant project """
project = Project.objects.get(pk=self.project_id)
return Build.objects.filter(project=project)

View File

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% load projecttags %}
{% load humanize %}

View File

@ -6,7 +6,7 @@
{%if mru and mru.count > 0%}
{%if mrb_type == 'project' %}
<h2>
<h2 class="page-header">
Latest project builds
{% if project.is_default %}

View File

@ -0,0 +1,56 @@
{% extends 'base.html' %}
{% load static %}
{% block extraheadcontent %}
<link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
<link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
<link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
<script src="{% static 'js/jquery-ui.min.js' %}">
</script>
{% endblock %}
{% block title %} {{title}} - {{project.name}} - Toaster {% endblock %}
{% block pagecontent %}
{% include "projecttopbar.html" %}
<div class="row-fluid">
{% with mru=mru mrb_type=mrb_type %}
{% include 'mrb_section.html' %}
{% endwith %}
<h2 class="page-header top-air" data-role="page-title"></h2>
{% url 'projectbuilds' project.id as xhr_table_url %}
{% include 'toastertable.html' %}
</div>
<script>
$(document).ready(function () {
// title
var tableElt = $("#{{table_name}}");
var titleElt = $("[data-role='page-title']");
tableElt.on("table-done", function (e, total, tableParams) {
var title = "All project builds";
if (tableParams.search || tableParams.filter) {
if (total === 0) {
title = "No project builds found";
}
else if (total > 0) {
title = total + " project build" + (total > 1 ? 's' : '') + " found";
}
}
titleElt.text(title);
});
// highlight builds tab
$("#topbar-builds-tab").addClass("active")
});
</script>
{% endblock %}

View File

@ -28,7 +28,7 @@ urlpatterns = patterns('toastergui.views',
url(r'^landing/$', 'landing', name='landing'),
url(r'^builds/$',
tables.BuildsTable.as_view(template_name="builds-toastertable.html"),
tables.AllBuildsTable.as_view(template_name="builds-toastertable.html"),
name='all-builds'),
# build info navigation
@ -83,7 +83,9 @@ urlpatterns = patterns('toastergui.views',
url(r'^project/(?P<pid>\d+)/$', 'project', name='project'),
url(r'^project/(?P<pid>\d+)/configuration$', 'projectconf', name='projectconf'),
url(r'^project/(?P<pid>\d+)/builds/$', 'projectbuilds', name='projectbuilds'),
url(r'^project/(?P<pid>\d+)/builds/$',
tables.ProjectBuildsTable.as_view(template_name="projectbuilds-toastertable.html"),
name='projectbuilds'),
# the import layer is a project-specific functionality;
url(r'^project/(?P<pid>\d+)/importlayer$', 'importlayer', name='importlayer'),

View File

@ -91,6 +91,7 @@ def landing(request):
return render(request, 'landing.html', context)
"""
# returns a list for most recent builds;
def _get_latest_builds(prj=None):
queryset = Build.objects.all()
@ -101,8 +102,9 @@ def _get_latest_builds(prj=None):
return list(itertools.chain(
queryset.filter(outcome=Build.IN_PROGRESS).order_by("-started_on"),
queryset.filter(outcome__lt=Build.IN_PROGRESS).order_by("-started_on")[:3] ))
"""
"""
# a JSON-able dict of recent builds; for use in the Project page, xhr_ updates, and other places, as needed
def _project_recent_build_list(prj):
data = []
@ -131,8 +133,7 @@ def _project_recent_build_list(prj):
data.append(d)
return data
"""
def objtojson(obj):
from django.db.models.query import QuerySet
@ -1915,6 +1916,7 @@ if True:
''' The exception raised on invalid POST requests '''
pass
"""
# helper function, to be used on "all builds" and "project builds" pages
def _build_list_helper(request, queryset_all, redirect_page, pid=None):
default_orderby = 'completed_on:-'
@ -2119,6 +2121,7 @@ if True:
# merge daterange values
context.update(context_date)
return context, pagesize, orderby
"""
@ -2256,7 +2259,7 @@ if True:
"completedbuilds": Build.objects.exclude(outcome = Build.IN_PROGRESS).filter(project_id = pid),
"prj" : {"name": prj.name, },
"buildrequests" : prj.build_set.filter(outcome=Build.IN_PROGRESS),
"builds" : _project_recent_build_list(prj),
#"builds" : _project_recent_build_list(prj),
"layers" : map(lambda x: {
"id": x.layercommit.pk,
"orderid": x.pk,
@ -2827,10 +2830,8 @@ if True:
# will set the GET parameters and redirect back to the
# all-builds or projectbuilds page as appropriate;
# TODO don't use exceptions to control program flow
@_template_renderer('projectbuilds.html')
"""
def projectbuilds(request, pid):
prj = Project.objects.get(id = pid)
if request.method == "POST":
# process any build request
@ -2880,6 +2881,7 @@ if True:
context['mru'] = _get_latest_builds(prj)
return context
"""
def _file_name_for_artifact(b, artifact_type, artifact_id):

View File

@ -61,7 +61,6 @@ class ToasterTable(TemplateView):
self.total_count = 0
self.static_context_extra = {}
self.filter_actions = {}
self.empty_state = "Sorry - no data found"
self.default_orderby = ""