summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/toastergui/tables.py
diff options
context:
space:
mode:
authorElliot Smith <elliot.smith@intel.com>2016-01-15 13:00:49 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-01-15 16:29:59 +0000
commit294579b531d5a96a17aa863554e71f4680d35812 (patch)
treed42f10f98cb26d233860eec59cbdf0799d53ae28 /bitbake/lib/toaster/toastergui/tables.py
parent6c12ca7f932d2fa06c2f2e2e6c98c76bb4d487d3 (diff)
downloadpoky-294579b531d5a96a17aa863554e71f4680d35812.tar.gz
bitbake: toastergui: convert all builds page to ToasterTable
For better long-term maintainability, use ToasterTable instead of Django template and view code to display the all builds page. NB the builds.html template has been left in, as this will otherwise cause conflicts when merging the new theme. [YOCTO #8738] (Bitbake rev: e0590fc8103afeb4c5e613a826057555c8193d59) 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>
Diffstat (limited to 'bitbake/lib/toaster/toastergui/tables.py')
-rw-r--r--bitbake/lib/toaster/toastergui/tables.py343
1 files changed, 302 insertions, 41 deletions
diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 2e3c8a6956..116cff3f43 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -20,29 +20,18 @@
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 21
22from toastergui.widgets import ToasterTable 22from toastergui.widgets import ToasterTable
23from toastergui.querysetfilter import QuerysetFilter
23from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project 24from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
24from orm.models import CustomImageRecipe, Package, Build 25from orm.models import CustomImageRecipe, Package, Build, LogMessage, Task
25from django.db.models import Q, Max, Count 26from django.db.models import Q, Max, Count
26from django.conf.urls import url 27from django.conf.urls import url
27from django.core.urlresolvers import reverse 28from django.core.urlresolvers import reverse
28from django.views.generic import TemplateView 29from django.views.generic import TemplateView
29 30
30class ProjectFiltersMixin(object): 31class ProjectFilters(object):
31 """Common mixin for recipe, machine in project filters""" 32 def __init__(self, project_layers):
32 33 self.in_project = QuerysetFilter(Q(layer_version__in=project_layers))
33 def filter_in_project(self, count_only=False): 34 self.not_in_project = QuerysetFilter(~Q(layer_version__in=project_layers))
34 query = self.queryset.filter(layer_version__in=self.project_layers)
35 if count_only:
36 return query.count()
37
38 self.queryset = query
39
40 def filter_not_in_project(self, count_only=False):
41 query = self.queryset.exclude(layer_version__in=self.project_layers)
42 if count_only:
43 return query.count()
44
45 self.queryset = query
46 35
47class LayersTable(ToasterTable): 36class LayersTable(ToasterTable):
48 """Table of layers in Toaster""" 37 """Table of layers in Toaster"""
@@ -60,34 +49,21 @@ class LayersTable(ToasterTable):
60 49
61 return context 50 return context
62 51
63
64 def setup_filters(self, *args, **kwargs): 52 def setup_filters(self, *args, **kwargs):
65 project = Project.objects.get(pk=kwargs['pid']) 53 project = Project.objects.get(pk=kwargs['pid'])
66 self.project_layers = ProjectLayer.objects.filter(project=project) 54 self.project_layers = ProjectLayer.objects.filter(project=project)
67 55
56 criteria = Q(projectlayer__in=self.project_layers)
57 in_project_filter = QuerysetFilter(criteria)
58 not_in_project_filter = QuerysetFilter(~criteria)
68 59
69 self.add_filter(title="Filter by project layers", 60 self.add_filter(title="Filter by project layers",
70 name="in_current_project", 61 name="in_current_project",
71 filter_actions=[ 62 filter_actions=[
72 self.make_filter_action("in_project", "Layers added to this project", self.filter_in_project), 63 self.make_filter_action("in_project", "Layers added to this project", in_project_filter),
73 self.make_filter_action("not_in_project", "Layers not added to this project", self.filter_not_in_project) 64 self.make_filter_action("not_in_project", "Layers not added to this project", not_in_project_filter)
74 ]) 65 ])
75 66
76 def filter_in_project(self, count_only=False):
77 query = self.queryset.filter(projectlayer__in=self.project_layers)
78 if count_only:
79 return query.count()
80
81 self.queryset = query
82
83 def filter_not_in_project(self, count_only=False):
84 query = self.queryset.exclude(projectlayer__in=self.project_layers)
85 if count_only:
86 return query.count()
87
88 self.queryset = query
89
90
91 def setup_queryset(self, *args, **kwargs): 67 def setup_queryset(self, *args, **kwargs):
92 prj = Project.objects.get(pk = kwargs['pid']) 68 prj = Project.objects.get(pk = kwargs['pid'])
93 compatible_layers = prj.get_all_compatible_layer_versions() 69 compatible_layers = prj.get_all_compatible_layer_versions()
@@ -204,7 +180,7 @@ class LayersTable(ToasterTable):
204 computation = lambda x: x.layer.name) 180 computation = lambda x: x.layer.name)
205 181
206 182
207class MachinesTable(ToasterTable, ProjectFiltersMixin): 183class MachinesTable(ToasterTable):
208 """Table of Machines in Toaster""" 184 """Table of Machines in Toaster"""
209 185
210 def __init__(self, *args, **kwargs): 186 def __init__(self, *args, **kwargs):
@@ -221,11 +197,13 @@ class MachinesTable(ToasterTable, ProjectFiltersMixin):
221 def setup_filters(self, *args, **kwargs): 197 def setup_filters(self, *args, **kwargs):
222 project = Project.objects.get(pk=kwargs['pid']) 198 project = Project.objects.get(pk=kwargs['pid'])
223 199
200 project_filters = ProjectFilters(self.project_layers)
201
224 self.add_filter(title="Filter by project machines", 202 self.add_filter(title="Filter by project machines",
225 name="in_current_project", 203 name="in_current_project",
226 filter_actions=[ 204 filter_actions=[
227 self.make_filter_action("in_project", "Machines provided by layers added to this project", self.filter_in_project), 205 self.make_filter_action("in_project", "Machines provided by layers added to this project", project_filters.in_project),
228 self.make_filter_action("not_in_project", "Machines provided by layers not added to this project", self.filter_not_in_project) 206 self.make_filter_action("not_in_project", "Machines provided by layers not added to this project", project_filters.not_in_project)
229 ]) 207 ])
230 208
231 def setup_queryset(self, *args, **kwargs): 209 def setup_queryset(self, *args, **kwargs):
@@ -313,7 +291,7 @@ class LayerMachinesTable(MachinesTable):
313 static_data_template=select_btn_template) 291 static_data_template=select_btn_template)
314 292
315 293
316class RecipesTable(ToasterTable, ProjectFiltersMixin): 294class RecipesTable(ToasterTable):
317 """Table of All Recipes in Toaster""" 295 """Table of All Recipes in Toaster"""
318 296
319 def __init__(self, *args, **kwargs): 297 def __init__(self, *args, **kwargs):
@@ -338,11 +316,13 @@ class RecipesTable(ToasterTable, ProjectFiltersMixin):
338 return context 316 return context
339 317
340 def setup_filters(self, *args, **kwargs): 318 def setup_filters(self, *args, **kwargs):
319 project_filters = ProjectFilters(self.project_layers)
320
341 self.add_filter(title="Filter by project recipes", 321 self.add_filter(title="Filter by project recipes",
342 name="in_current_project", 322 name="in_current_project",
343 filter_actions=[ 323 filter_actions=[
344 self.make_filter_action("in_project", "Recipes provided by layers added to this project", self.filter_in_project), 324 self.make_filter_action("in_project", "Recipes provided by layers added to this project", project_filters.in_project),
345 self.make_filter_action("not_in_project", "Recipes provided by layers not added to this project", self.filter_not_in_project) 325 self.make_filter_action("not_in_project", "Recipes provided by layers not added to this project", project_filters.not_in_project)
346 ]) 326 ])
347 327
348 def setup_queryset(self, *args, **kwargs): 328 def setup_queryset(self, *args, **kwargs):
@@ -853,3 +833,284 @@ class ProjectsTable(ToasterTable):
853 orderable=False, 833 orderable=False,
854 static_data_name='image_files', 834 static_data_name='image_files',
855 static_data_template=image_files_template) 835 static_data_template=image_files_template)
836
837class BuildsTable(ToasterTable):
838 """Table of builds in Toaster"""
839
840 def __init__(self, *args, **kwargs):
841 super(BuildsTable, self).__init__(*args, **kwargs)
842 self.default_orderby = '-completed_on'
843 self.title = 'All builds'
844 self.static_context_extra['Build'] = Build
845 self.static_context_extra['Task'] = Task
846
847 def get_context_data(self, **kwargs):
848 return super(BuildsTable, self).get_context_data(**kwargs)
849
850 def setup_queryset(self, *args, **kwargs):
851 queryset = Build.objects.all()
852
853 # don't include in progress builds
854 queryset = queryset.exclude(outcome=Build.IN_PROGRESS)
855
856 # sort
857 queryset = queryset.order_by(self.default_orderby)
858
859 # annotate with number of ERROR and EXCEPTION log messages
860 queryset = queryset.annotate(
861 errors_no = Count(
862 'logmessage',
863 only = Q(logmessage__level=LogMessage.ERROR) |
864 Q(logmessage__level=LogMessage.EXCEPTION)
865 )
866 )
867
868 # annotate with number of WARNING log messages
869 queryset = queryset.annotate(
870 warnings_no = Count(
871 'logmessage',
872 only = Q(logmessage__level=LogMessage.WARNING)
873 )
874 )
875
876 self.queryset = queryset
877
878 def setup_columns(self, *args, **kwargs):
879 outcome_template = '''
880 <a href="{% url "builddashboard" data.id %}">
881 {% if data.outcome == data.SUCCEEDED %}
882 <i class="icon-ok-sign success"></i>
883 {% elif data.outcome == data.FAILED %}
884 <i class="icon-minus-sign error"></i>
885 {% endif %}
886 </a>
887
888 {% if data.cooker_log_path %}
889 &nbsp;
890 <a href="{% url "build_artifact" data.id "cookerlog" data.id %}">
891 <i class="icon-download-alt" title="Download build log"></i>
892 </a>
893 {% endif %}
894 '''
895
896 recipe_template = '''
897 {% for target_label in data.target_labels %}
898 <a href="{% url "builddashboard" data.id %}">
899 {{target_label}}
900 </a>
901 <br />
902 {% endfor %}
903 '''
904
905 machine_template = '''
906 <a href="{% url "builddashboard" data.id %}">
907 {{data.machine}}
908 </a>
909 '''
910
911 started_on_template = '''
912 <a href="{% url "builddashboard" data.id %}">
913 {{data.started_on | date:"d/m/y H:i"}}
914 </a>
915 '''
916
917 completed_on_template = '''
918 <a href="{% url "builddashboard" data.id %}">
919 {{data.completed_on | date:"d/m/y H:i"}}
920 </a>
921 '''
922
923 failed_tasks_template = '''
924 {% if data.failed_tasks.count == 1 %}
925 <a href="{% url "task" data.id data.failed_tasks.0.id %}">
926 <span class="error">
927 {{data.failed_tasks.0.recipe.name}}.{{data.failed_tasks.0.task_name}}
928 </span>
929 </a>
930 <a href="{% url "build_artifact" data.id "tasklogfile" data.failed_tasks.0.id %}">
931 <i class="icon-download-alt"
932 data-original-title="Download task log file">
933 </i>
934 </a>
935 {% elif data.failed_tasks.count > 1 %}
936 <a href="{% url "tasks" data.id %}?filter=outcome%3A{{extra.Task.OUTCOME_FAILED}}">
937 <span class="error">{{data.failed_tasks.count}} tasks</span>
938 </a>
939 {% endif %}
940 '''
941
942 errors_template = '''
943 {% if data.errors.count %}
944 <a class="errors.count error" href="{% url "builddashboard" data.id %}#errors">
945 {{data.errors.count}} error{{data.errors.count|pluralize}}
946 </a>
947 {% endif %}
948 '''
949
950 warnings_template = '''
951 {% if data.warnings.count %}
952 <a class="warnings.count warning" href="{% url "builddashboard" data.id %}#warnings">
953 {{data.warnings.count}} warning{{data.warnings.count|pluralize}}
954 </a>
955 {% endif %}
956 '''
957
958 time_template = '''
959 {% load projecttags %}
960 <a href="{% url "buildtime" data.id %}">
961 {{data.timespent_seconds | sectohms}}
962 </a>
963 '''
964
965 image_files_template = '''
966 {% if data.outcome == extra.Build.SUCCEEDED %}
967 <a href="{% url "builddashboard" data.id %}#images">
968 {{data.get_image_file_extensions}}
969 </a>
970 {% endif %}
971 '''
972
973 project_template = '''
974 {% load project_url_tag %}
975 <a href="{% project_url data.project %}">
976 {{data.project.name}}
977 </a>
978 {% if data.project.is_default %}
979 <i class="icon-question-sign get-help hover-help" title=""
980 data-original-title="This project shows information about
981 the builds you start from the command line while Toaster is
982 running" style="visibility: hidden;"></i>
983 {% endif %}
984 '''
985
986 self.add_column(title='Outcome',
987 help_text='Final state of the build (successful \
988 or failed)',
989 hideable=False,
990 orderable=True,
991 filter_name='outcome_filter',
992 static_data_name='outcome',
993 static_data_template=outcome_template)
994
995 self.add_column(title='Recipe',
996 help_text='What was built (i.e. one or more recipes \
997 or image recipes)',
998 hideable=False,
999 orderable=False,
1000 static_data_name='target',
1001 static_data_template=recipe_template)
1002
1003 self.add_column(title='Machine',
1004 help_text='Hardware for which you are building a \
1005 recipe or image recipe',
1006 hideable=False,
1007 orderable=True,
1008 static_data_name='machine',
1009 static_data_template=machine_template)
1010
1011 self.add_column(title='Started on',
1012 help_text='The date and time when the build started',
1013 hideable=True,
1014 orderable=True,
1015 static_data_name='started_on',
1016 static_data_template=started_on_template)
1017
1018 self.add_column(title='Completed on',
1019 help_text='The date and time when the build finished',
1020 hideable=False,
1021 orderable=True,
1022 static_data_name='completed_on',
1023 static_data_template=completed_on_template)
1024
1025 self.add_column(title='Failed tasks',
1026 help_text='The number of tasks which failed during \
1027 the build',
1028 hideable=True,
1029 orderable=False,
1030 filter_name='failed_tasks_filter',
1031 static_data_name='failed_tasks',
1032 static_data_template=failed_tasks_template)
1033
1034 self.add_column(title='Errors',
1035 help_text='The number of errors encountered during \
1036 the build (if any)',
1037 hideable=True,
1038 orderable=False,
1039 static_data_name='errors',
1040 static_data_template=errors_template)
1041
1042 self.add_column(title='Warnings',
1043 help_text='The number of warnings encountered during \
1044 the build (if any)',
1045 hideable=True,
1046 orderable=False,
1047 static_data_name='warnings',
1048 static_data_template=warnings_template)
1049
1050 self.add_column(title='Time',
1051 help_text='How long the build took to finish',
1052 hideable=False,
1053 orderable=False,
1054 static_data_name='time',
1055 static_data_template=time_template)
1056
1057 self.add_column(title='Image files',
1058 help_text='The root file system types produced by \
1059 the build',
1060 hideable=True,
1061 orderable=False,
1062 static_data_name='image_files',
1063 static_data_template=image_files_template)
1064
1065 self.add_column(title='Project',
1066 hideable=True,
1067 orderable=False,
1068 static_data_name='project-name',
1069 static_data_template=project_template)
1070
1071 def setup_filters(self, *args, **kwargs):
1072 # outcomes
1073 filter_only_successful_builds = QuerysetFilter(Q(outcome=Build.SUCCEEDED))
1074 successful_builds_filter = self.make_filter_action(
1075 'successful_builds',
1076 'Successful builds',
1077 filter_only_successful_builds
1078 )
1079
1080 filter_only_failed_builds = QuerysetFilter(Q(outcome=Build.FAILED))
1081 failed_builds_filter = self.make_filter_action(
1082 'failed_builds',
1083 'Failed builds',
1084 filter_only_failed_builds
1085 )
1086
1087 self.add_filter(title='Filter builds by outcome',
1088 name='outcome_filter',
1089 filter_actions = [
1090 successful_builds_filter,
1091 failed_builds_filter
1092 ])
1093
1094 # failed tasks
1095 criteria = Q(task_build__outcome=Task.OUTCOME_FAILED)
1096 filter_only_builds_with_failed_tasks = QuerysetFilter(criteria)
1097 with_failed_tasks_filter = self.make_filter_action(
1098 'with_failed_tasks',
1099 'Builds with failed tasks',
1100 filter_only_builds_with_failed_tasks
1101 )
1102
1103 criteria = ~Q(task_build__outcome=Task.OUTCOME_FAILED)
1104 filter_only_builds_without_failed_tasks = QuerysetFilter(criteria)
1105 without_failed_tasks_filter = self.make_filter_action(
1106 'without_failed_tasks',
1107 'Builds without failed tasks',
1108 filter_only_builds_without_failed_tasks
1109 )
1110
1111 self.add_filter(title='Filter builds by failed tasks',
1112 name='failed_tasks_filter',
1113 filter_actions = [
1114 with_failed_tasks_filter,
1115 without_failed_tasks_filter
1116 ])