1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# BitBake Toaster Implementation 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# Copyright (C) 2016 Intel Corporation 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 7*4882a593Smuzhiyun# 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunfrom orm.models import Build, Task, Target, Package 10*4882a593Smuzhiyunfrom django.db.models import Q, Sum 11*4882a593Smuzhiyun 12*4882a593Smuzhiyunimport toastergui.tables as tables 13*4882a593Smuzhiyunfrom toastergui.widgets import ToasterTable 14*4882a593Smuzhiyunfrom toastergui.tablefilter import TableFilter 15*4882a593Smuzhiyunfrom toastergui.tablefilter import TableFilterActionToggle 16*4882a593Smuzhiyun 17*4882a593Smuzhiyun 18*4882a593Smuzhiyunclass BuildTablesMixin(ToasterTable): 19*4882a593Smuzhiyun def get_context_data(self, **kwargs): 20*4882a593Smuzhiyun # We need to be explicit about which superclass we're calling here 21*4882a593Smuzhiyun # Otherwise the MRO gets in a right mess 22*4882a593Smuzhiyun context = ToasterTable.get_context_data(self, **kwargs) 23*4882a593Smuzhiyun context['build'] = Build.objects.get(pk=kwargs['build_id']) 24*4882a593Smuzhiyun return context 25*4882a593Smuzhiyun 26*4882a593Smuzhiyun 27*4882a593Smuzhiyunclass BuiltPackagesTableBase(tables.PackagesTable): 28*4882a593Smuzhiyun """ Table to display all the packages built in a build """ 29*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 30*4882a593Smuzhiyun super(BuiltPackagesTableBase, self).__init__(*args, **kwargs) 31*4882a593Smuzhiyun self.title = "Packages built" 32*4882a593Smuzhiyun self.default_orderby = "name" 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 35*4882a593Smuzhiyun build = Build.objects.get(pk=kwargs['build_id']) 36*4882a593Smuzhiyun self.static_context_extra['build'] = build 37*4882a593Smuzhiyun self.static_context_extra['target_name'] = None 38*4882a593Smuzhiyun self.queryset = build.package_set.all().exclude(recipe=None) 39*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 40*4882a593Smuzhiyun 41*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 42*4882a593Smuzhiyun super(BuiltPackagesTableBase, self).setup_columns(*args, **kwargs) 43*4882a593Smuzhiyun 44*4882a593Smuzhiyun def pkg_link_template(val): 45*4882a593Smuzhiyun """ return the template used for the link with the val as the 46*4882a593Smuzhiyun element value i.e. inside the <a></a>""" 47*4882a593Smuzhiyun 48*4882a593Smuzhiyun return (''' 49*4882a593Smuzhiyun <a href=" 50*4882a593Smuzhiyun {%% url "package_built_detail" extra.build.pk data.pk %%} 51*4882a593Smuzhiyun ">%s</a> 52*4882a593Smuzhiyun ''' % val) 53*4882a593Smuzhiyun 54*4882a593Smuzhiyun def recipe_link_template(val): 55*4882a593Smuzhiyun return (''' 56*4882a593Smuzhiyun {%% if data.recipe %%} 57*4882a593Smuzhiyun <a href=" 58*4882a593Smuzhiyun {%% url "recipe" extra.build.pk data.recipe.pk %%} 59*4882a593Smuzhiyun ">%(value)s</a> 60*4882a593Smuzhiyun {%% else %%} 61*4882a593Smuzhiyun %(value)s 62*4882a593Smuzhiyun {%% endif %%} 63*4882a593Smuzhiyun ''' % {'value': val}) 64*4882a593Smuzhiyun 65*4882a593Smuzhiyun add_pkg_link_to = 'name' 66*4882a593Smuzhiyun add_recipe_link_to = 'recipe__name' 67*4882a593Smuzhiyun 68*4882a593Smuzhiyun # Add the recipe and pkg build links to the required columns 69*4882a593Smuzhiyun for column in self.columns: 70*4882a593Smuzhiyun # Convert to template field style accessors 71*4882a593Smuzhiyun tmplv = column['field_name'].replace('__', '.') 72*4882a593Smuzhiyun tmplv = "{{data.%s}}" % tmplv 73*4882a593Smuzhiyun 74*4882a593Smuzhiyun if column['field_name'] is add_pkg_link_to: 75*4882a593Smuzhiyun # Don't overwrite an existing template 76*4882a593Smuzhiyun if column['static_data_template']: 77*4882a593Smuzhiyun column['static_data_template'] =\ 78*4882a593Smuzhiyun pkg_link_template(column['static_data_template']) 79*4882a593Smuzhiyun else: 80*4882a593Smuzhiyun column['static_data_template'] = pkg_link_template(tmplv) 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun column['static_data_name'] = column['field_name'] 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun elif column['field_name'] is add_recipe_link_to: 85*4882a593Smuzhiyun # Don't overwrite an existing template 86*4882a593Smuzhiyun if column['static_data_template']: 87*4882a593Smuzhiyun column['static_data_template'] =\ 88*4882a593Smuzhiyun recipe_link_template(column['static_data_template']) 89*4882a593Smuzhiyun else: 90*4882a593Smuzhiyun column['static_data_template'] =\ 91*4882a593Smuzhiyun recipe_link_template(tmplv) 92*4882a593Smuzhiyun column['static_data_name'] = column['field_name'] 93*4882a593Smuzhiyun 94*4882a593Smuzhiyun self.add_column(title="Layer", 95*4882a593Smuzhiyun field_name="recipe__layer_version__layer__name", 96*4882a593Smuzhiyun hidden=True, 97*4882a593Smuzhiyun orderable=True) 98*4882a593Smuzhiyun 99*4882a593Smuzhiyun layer_branch_template = ''' 100*4882a593Smuzhiyun {%if not data.recipe.layer_version.layer.local_source_dir %} 101*4882a593Smuzhiyun <span class="text-muted">{{data.recipe.layer_version.branch}}</span> 102*4882a593Smuzhiyun {% else %} 103*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 104*4882a593Smuzhiyun <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.recipe.layer_version.layer.name}} is not in a Git repository, so there is no branch associated with it"> </span> 105*4882a593Smuzhiyun {% endif %} 106*4882a593Smuzhiyun ''' 107*4882a593Smuzhiyun 108*4882a593Smuzhiyun self.add_column(title="Layer branch", 109*4882a593Smuzhiyun field_name="recipe__layer_version__branch", 110*4882a593Smuzhiyun hidden=True, 111*4882a593Smuzhiyun static_data_name="recipe__layer_version__branch", 112*4882a593Smuzhiyun static_data_template=layer_branch_template, 113*4882a593Smuzhiyun orderable=True) 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun git_rev_template = ''' 116*4882a593Smuzhiyun {% if not data.recipe.layer_version.layer.local_source_dir %} 117*4882a593Smuzhiyun {% with vcs_ref=data.recipe.layer_version.commit %} 118*4882a593Smuzhiyun {% include 'snippets/gitrev_popover.html' %} 119*4882a593Smuzhiyun {% endwith %} 120*4882a593Smuzhiyun {% else %} 121*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 122*4882a593Smuzhiyun <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.recipe.layer_version.layer.name}} is not in a Git repository, so there is no revision associated with it"> </span> 123*4882a593Smuzhiyun {% endif %} 124*4882a593Smuzhiyun ''' 125*4882a593Smuzhiyun 126*4882a593Smuzhiyun self.add_column(title="Layer commit", 127*4882a593Smuzhiyun static_data_name='vcs_ref', 128*4882a593Smuzhiyun static_data_template=git_rev_template, 129*4882a593Smuzhiyun hidden=True) 130*4882a593Smuzhiyun 131*4882a593Smuzhiyun 132*4882a593Smuzhiyunclass BuiltPackagesTable(BuildTablesMixin, BuiltPackagesTableBase): 133*4882a593Smuzhiyun """ Show all the packages built for the selected build """ 134*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 135*4882a593Smuzhiyun super(BuiltPackagesTable, self).__init__(*args, **kwargs) 136*4882a593Smuzhiyun self.title = "Packages built" 137*4882a593Smuzhiyun self.default_orderby = "name" 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun self.empty_state =\ 140*4882a593Smuzhiyun ('<strong>No packages were built.</strong> How did this happen? ' 141*4882a593Smuzhiyun 'Well, BitBake reuses as much stuff as possible. ' 142*4882a593Smuzhiyun 'If all of the packages needed were already built and available ' 143*4882a593Smuzhiyun 'in your build infrastructure, BitBake ' 144*4882a593Smuzhiyun 'will not rebuild any of them. This might be slightly confusing, ' 145*4882a593Smuzhiyun 'but it does make everything faster.') 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 148*4882a593Smuzhiyun super(BuiltPackagesTable, self).setup_columns(*args, **kwargs) 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun def remove_dep_cols(columns): 151*4882a593Smuzhiyun for column in columns: 152*4882a593Smuzhiyun # We don't need these fields 153*4882a593Smuzhiyun if column['static_data_name'] in ['reverse_dependencies', 154*4882a593Smuzhiyun 'dependencies']: 155*4882a593Smuzhiyun continue 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun yield column 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun self.columns = list(remove_dep_cols(self.columns)) 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun 162*4882a593Smuzhiyunclass InstalledPackagesTable(BuildTablesMixin, BuiltPackagesTableBase): 163*4882a593Smuzhiyun """ Show all packages installed in an image """ 164*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 165*4882a593Smuzhiyun super(InstalledPackagesTable, self).__init__(*args, **kwargs) 166*4882a593Smuzhiyun self.title = "Packages Included" 167*4882a593Smuzhiyun self.default_orderby = "name" 168*4882a593Smuzhiyun 169*4882a593Smuzhiyun def make_package_list(self, target): 170*4882a593Smuzhiyun # The database design means that you get the intermediate objects and 171*4882a593Smuzhiyun # not package objects like you'd really want so we get them here 172*4882a593Smuzhiyun pkgs = target.target_installed_package_set.values_list('package', 173*4882a593Smuzhiyun flat=True) 174*4882a593Smuzhiyun return Package.objects.filter(pk__in=pkgs) 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun def get_context_data(self, **kwargs): 177*4882a593Smuzhiyun context = super(InstalledPackagesTable, 178*4882a593Smuzhiyun self).get_context_data(**kwargs) 179*4882a593Smuzhiyun 180*4882a593Smuzhiyun target = Target.objects.get(pk=kwargs['target_id']) 181*4882a593Smuzhiyun packages = self.make_package_list(target) 182*4882a593Smuzhiyun 183*4882a593Smuzhiyun context['packages_sum'] = packages.aggregate( 184*4882a593Smuzhiyun Sum('installed_size'))['installed_size__sum'] 185*4882a593Smuzhiyun 186*4882a593Smuzhiyun context['target'] = target 187*4882a593Smuzhiyun return context 188*4882a593Smuzhiyun 189*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 190*4882a593Smuzhiyun build = Build.objects.get(pk=kwargs['build_id']) 191*4882a593Smuzhiyun self.static_context_extra['build'] = build 192*4882a593Smuzhiyun 193*4882a593Smuzhiyun target = Target.objects.get(pk=kwargs['target_id']) 194*4882a593Smuzhiyun # We send these separately because in the case of image details table 195*4882a593Smuzhiyun # we don't have a target just the recipe name as the target 196*4882a593Smuzhiyun self.static_context_extra['target_name'] = target.target 197*4882a593Smuzhiyun self.static_context_extra['target_id'] = target.pk 198*4882a593Smuzhiyun 199*4882a593Smuzhiyun self.static_context_extra['add_links'] = True 200*4882a593Smuzhiyun 201*4882a593Smuzhiyun self.queryset = self.make_package_list(target) 202*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 203*4882a593Smuzhiyun 204*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 205*4882a593Smuzhiyun super(InstalledPackagesTable, self).setup_columns(**kwargs) 206*4882a593Smuzhiyun self.add_column(title="Installed size", 207*4882a593Smuzhiyun static_data_name="installed_size", 208*4882a593Smuzhiyun static_data_template="{% load projecttags %}" 209*4882a593Smuzhiyun "{{data.size|filtered_filesizeformat}}", 210*4882a593Smuzhiyun orderable=True, 211*4882a593Smuzhiyun hidden=True) 212*4882a593Smuzhiyun 213*4882a593Smuzhiyun # Add the template to show installed name for installed packages 214*4882a593Smuzhiyun install_name_tmpl =\ 215*4882a593Smuzhiyun ('<a href="{% url "package_included_detail" extra.build.pk' 216*4882a593Smuzhiyun ' extra.target_id data.pk %}">{{data.name}}</a>' 217*4882a593Smuzhiyun '{% if data.installed_name and data.installed_name !=' 218*4882a593Smuzhiyun ' data.name %}' 219*4882a593Smuzhiyun '<span class="text-muted"> as {{data.installed_name}}</span>' 220*4882a593Smuzhiyun ' <span class="glyphicon glyphicon-question-sign get-help hover-help"' 221*4882a593Smuzhiyun ' title="{{data.name}} was renamed at packaging time and' 222*4882a593Smuzhiyun ' was installed in your image as {{data.installed_name}}' 223*4882a593Smuzhiyun '"></span>{% endif %} ') 224*4882a593Smuzhiyun 225*4882a593Smuzhiyun for column in self.columns: 226*4882a593Smuzhiyun if column['static_data_name'] == 'name': 227*4882a593Smuzhiyun column['static_data_template'] = install_name_tmpl 228*4882a593Smuzhiyun break 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun 231*4882a593Smuzhiyunclass BuiltRecipesTable(BuildTablesMixin): 232*4882a593Smuzhiyun """ Table to show the recipes that have been built in this build """ 233*4882a593Smuzhiyun 234*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 235*4882a593Smuzhiyun super(BuiltRecipesTable, self).__init__(*args, **kwargs) 236*4882a593Smuzhiyun self.title = "Recipes built" 237*4882a593Smuzhiyun self.default_orderby = "name" 238*4882a593Smuzhiyun 239*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 240*4882a593Smuzhiyun build = Build.objects.get(pk=kwargs['build_id']) 241*4882a593Smuzhiyun self.static_context_extra['build'] = build 242*4882a593Smuzhiyun self.queryset = build.get_recipes() 243*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 244*4882a593Smuzhiyun 245*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 246*4882a593Smuzhiyun recipe_name_tmpl =\ 247*4882a593Smuzhiyun '<a href="{% url "recipe" extra.build.pk data.pk %}">'\ 248*4882a593Smuzhiyun '{{data.name}}'\ 249*4882a593Smuzhiyun '</a>' 250*4882a593Smuzhiyun 251*4882a593Smuzhiyun recipe_file_tmpl =\ 252*4882a593Smuzhiyun '{{data.file_path}}'\ 253*4882a593Smuzhiyun '{% if data.pathflags %}<i>({{data.pathflags}})</i>'\ 254*4882a593Smuzhiyun '{% endif %}' 255*4882a593Smuzhiyun 256*4882a593Smuzhiyun git_branch_template = ''' 257*4882a593Smuzhiyun {% if data.layer_version.layer.local_source_dir %} 258*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 259*4882a593Smuzhiyun <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer_version.layer.name}} is not in a Git repository, so there is no branch associated with it"> </span> 260*4882a593Smuzhiyun {% else %} 261*4882a593Smuzhiyun <span>{{data.layer_version.branch}}</span> 262*4882a593Smuzhiyun {% endif %} 263*4882a593Smuzhiyun ''' 264*4882a593Smuzhiyun 265*4882a593Smuzhiyun git_rev_template = ''' 266*4882a593Smuzhiyun {% if data.layer_version.layer.local_source_dir %} 267*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 268*4882a593Smuzhiyun <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer_version.layer.name}} is not in a Git repository, so there is no commit associated with it"> </span> 269*4882a593Smuzhiyun {% else %} 270*4882a593Smuzhiyun {% with vcs_ref=data.layer_version.commit %} 271*4882a593Smuzhiyun {% include 'snippets/gitrev_popover.html' %} 272*4882a593Smuzhiyun {% endwith %} 273*4882a593Smuzhiyun {% endif %} 274*4882a593Smuzhiyun ''' 275*4882a593Smuzhiyun 276*4882a593Smuzhiyun depends_on_tmpl = ''' 277*4882a593Smuzhiyun {% with deps=data.r_dependencies_recipe.all %} 278*4882a593Smuzhiyun {% with count=deps|length %} 279*4882a593Smuzhiyun {% if count %} 280*4882a593Smuzhiyun <a class="btn btn-default" title=" 281*4882a593Smuzhiyun <a href='{% url "recipe" extra.build.pk data.pk %}#dependencies'> 282*4882a593Smuzhiyun {{data.name}}</a> dependencies" 283*4882a593Smuzhiyun data-content="<ul class='list-unstyled'> 284*4882a593Smuzhiyun {% for dep in deps|dictsort:"depends_on.name"%} 285*4882a593Smuzhiyun <li><a href='{% url "recipe" extra.build.pk dep.depends_on.pk %}'> 286*4882a593Smuzhiyun {{dep.depends_on.name}}</a></li> 287*4882a593Smuzhiyun {% endfor %} 288*4882a593Smuzhiyun </ul>"> 289*4882a593Smuzhiyun {{count}} 290*4882a593Smuzhiyun </a> 291*4882a593Smuzhiyun {% endif %}{% endwith %}{% endwith %} 292*4882a593Smuzhiyun ''' 293*4882a593Smuzhiyun 294*4882a593Smuzhiyun rev_depends_tmpl = ''' 295*4882a593Smuzhiyun {% with revs=data.r_dependencies_depends.all %} 296*4882a593Smuzhiyun {% with count=revs|length %} 297*4882a593Smuzhiyun {% if count %} 298*4882a593Smuzhiyun <a class="btn btn-default" 299*4882a593Smuzhiyun title=" 300*4882a593Smuzhiyun <a href='{% url "recipe" extra.build.pk data.pk %}#brought-in-by'> 301*4882a593Smuzhiyun {{data.name}}</a> reverse dependencies" 302*4882a593Smuzhiyun data-content="<ul class='list-unstyled'> 303*4882a593Smuzhiyun {% for dep in revs|dictsort:"recipe.name" %} 304*4882a593Smuzhiyun <li> 305*4882a593Smuzhiyun <a href='{% url "recipe" extra.build.pk dep.recipe.pk %}'> 306*4882a593Smuzhiyun {{dep.recipe.name}} 307*4882a593Smuzhiyun </a></li> 308*4882a593Smuzhiyun {% endfor %} 309*4882a593Smuzhiyun </ul>"> 310*4882a593Smuzhiyun {{count}} 311*4882a593Smuzhiyun </a> 312*4882a593Smuzhiyun {% endif %}{% endwith %}{% endwith %} 313*4882a593Smuzhiyun ''' 314*4882a593Smuzhiyun 315*4882a593Smuzhiyun self.add_column(title="Recipe", 316*4882a593Smuzhiyun field_name="name", 317*4882a593Smuzhiyun static_data_name='name', 318*4882a593Smuzhiyun orderable=True, 319*4882a593Smuzhiyun hideable=False, 320*4882a593Smuzhiyun static_data_template=recipe_name_tmpl) 321*4882a593Smuzhiyun 322*4882a593Smuzhiyun self.add_column(title="Version", 323*4882a593Smuzhiyun hideable=False, 324*4882a593Smuzhiyun field_name="version") 325*4882a593Smuzhiyun 326*4882a593Smuzhiyun self.add_column(title="Dependencies", 327*4882a593Smuzhiyun static_data_name="dependencies", 328*4882a593Smuzhiyun static_data_template=depends_on_tmpl) 329*4882a593Smuzhiyun 330*4882a593Smuzhiyun self.add_column(title="Reverse dependencies", 331*4882a593Smuzhiyun static_data_name="revdeps", 332*4882a593Smuzhiyun static_data_template=rev_depends_tmpl, 333*4882a593Smuzhiyun help_text='Recipe build-time reverse dependencies' 334*4882a593Smuzhiyun ' (i.e. the recipes that depend on this recipe)') 335*4882a593Smuzhiyun 336*4882a593Smuzhiyun self.add_column(title="Recipe file", 337*4882a593Smuzhiyun field_name="file_path", 338*4882a593Smuzhiyun static_data_name="file_path", 339*4882a593Smuzhiyun static_data_template=recipe_file_tmpl, 340*4882a593Smuzhiyun hidden=True) 341*4882a593Smuzhiyun 342*4882a593Smuzhiyun self.add_column(title="Section", 343*4882a593Smuzhiyun field_name="section", 344*4882a593Smuzhiyun orderable=True, 345*4882a593Smuzhiyun hidden=True) 346*4882a593Smuzhiyun 347*4882a593Smuzhiyun self.add_column(title="License", 348*4882a593Smuzhiyun field_name="license", 349*4882a593Smuzhiyun help_text='Multiple license names separated by the' 350*4882a593Smuzhiyun ' pipe character indicates a choice between licenses.' 351*4882a593Smuzhiyun ' Multiple license names separated by the ampersand' 352*4882a593Smuzhiyun ' character indicates multiple licenses exist that' 353*4882a593Smuzhiyun ' cover different parts of the source', 354*4882a593Smuzhiyun orderable=True) 355*4882a593Smuzhiyun 356*4882a593Smuzhiyun self.add_column(title="Layer", 357*4882a593Smuzhiyun field_name="layer_version__layer__name", 358*4882a593Smuzhiyun orderable=True) 359*4882a593Smuzhiyun 360*4882a593Smuzhiyun self.add_column(title="Layer branch", 361*4882a593Smuzhiyun field_name="layer_version__branch", 362*4882a593Smuzhiyun static_data_name="layer_version__branch", 363*4882a593Smuzhiyun static_data_template=git_branch_template, 364*4882a593Smuzhiyun orderable=True, 365*4882a593Smuzhiyun hidden=True) 366*4882a593Smuzhiyun 367*4882a593Smuzhiyun self.add_column(title="Layer commit", 368*4882a593Smuzhiyun static_data_name="commit", 369*4882a593Smuzhiyun static_data_template=git_rev_template, 370*4882a593Smuzhiyun hidden=True) 371*4882a593Smuzhiyun 372*4882a593Smuzhiyun 373*4882a593Smuzhiyunclass BuildTasksTable(BuildTablesMixin): 374*4882a593Smuzhiyun """ Table to show the tasks that run in this build """ 375*4882a593Smuzhiyun 376*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 377*4882a593Smuzhiyun super(BuildTasksTable, self).__init__(*args, **kwargs) 378*4882a593Smuzhiyun self.title = "Tasks" 379*4882a593Smuzhiyun self.default_orderby = "order" 380*4882a593Smuzhiyun 381*4882a593Smuzhiyun # Toggle these columns on off for Time/CPU usage/Disk I/O tables 382*4882a593Smuzhiyun self.toggle_columns = {} 383*4882a593Smuzhiyun 384*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 385*4882a593Smuzhiyun build = Build.objects.get(pk=kwargs['build_id']) 386*4882a593Smuzhiyun self.static_context_extra['build'] = build 387*4882a593Smuzhiyun self.queryset = build.task_build.filter(~Q(order=None)) 388*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 389*4882a593Smuzhiyun 390*4882a593Smuzhiyun def setup_filters(self, *args, **kwargs): 391*4882a593Smuzhiyun # Execution outcome types filter 392*4882a593Smuzhiyun executed_outcome = TableFilter(name="execution_outcome", 393*4882a593Smuzhiyun title="Filter Tasks by 'Executed") 394*4882a593Smuzhiyun 395*4882a593Smuzhiyun exec_outcome_action_exec = TableFilterActionToggle( 396*4882a593Smuzhiyun "executed", 397*4882a593Smuzhiyun "Executed Tasks", 398*4882a593Smuzhiyun Q(task_executed=True)) 399*4882a593Smuzhiyun 400*4882a593Smuzhiyun exec_outcome_action_not_exec = TableFilterActionToggle( 401*4882a593Smuzhiyun "not_executed", 402*4882a593Smuzhiyun "Not Executed Tasks", 403*4882a593Smuzhiyun Q(task_executed=False)) 404*4882a593Smuzhiyun 405*4882a593Smuzhiyun executed_outcome.add_action(exec_outcome_action_exec) 406*4882a593Smuzhiyun executed_outcome.add_action(exec_outcome_action_not_exec) 407*4882a593Smuzhiyun 408*4882a593Smuzhiyun # Task outcome types filter 409*4882a593Smuzhiyun task_outcome = TableFilter(name="task_outcome", 410*4882a593Smuzhiyun title="Filter Task by 'Outcome'") 411*4882a593Smuzhiyun 412*4882a593Smuzhiyun for outcome_enum, title in Task.TASK_OUTCOME: 413*4882a593Smuzhiyun if outcome_enum is Task.OUTCOME_NA: 414*4882a593Smuzhiyun continue 415*4882a593Smuzhiyun action = TableFilterActionToggle( 416*4882a593Smuzhiyun title.replace(" ", "_").lower(), 417*4882a593Smuzhiyun "%s Tasks" % title, 418*4882a593Smuzhiyun Q(outcome=outcome_enum)) 419*4882a593Smuzhiyun 420*4882a593Smuzhiyun task_outcome.add_action(action) 421*4882a593Smuzhiyun 422*4882a593Smuzhiyun # SSTATE outcome types filter 423*4882a593Smuzhiyun sstate_outcome = TableFilter(name="sstate_outcome", 424*4882a593Smuzhiyun title="Filter Task by 'Cache attempt'") 425*4882a593Smuzhiyun 426*4882a593Smuzhiyun for sstate_result_enum, title in Task.SSTATE_RESULT: 427*4882a593Smuzhiyun action = TableFilterActionToggle( 428*4882a593Smuzhiyun title.replace(" ", "_").lower(), 429*4882a593Smuzhiyun "Tasks with '%s' attempts" % title, 430*4882a593Smuzhiyun Q(sstate_result=sstate_result_enum)) 431*4882a593Smuzhiyun 432*4882a593Smuzhiyun sstate_outcome.add_action(action) 433*4882a593Smuzhiyun 434*4882a593Smuzhiyun self.add_filter(sstate_outcome) 435*4882a593Smuzhiyun self.add_filter(executed_outcome) 436*4882a593Smuzhiyun self.add_filter(task_outcome) 437*4882a593Smuzhiyun 438*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 439*4882a593Smuzhiyun self.toggle_columns['order'] = len(self.columns) 440*4882a593Smuzhiyun 441*4882a593Smuzhiyun recipe_name_tmpl =\ 442*4882a593Smuzhiyun '<a href="{% url "recipe" extra.build.pk data.recipe.pk %}">'\ 443*4882a593Smuzhiyun '{{data.recipe.name}}'\ 444*4882a593Smuzhiyun '</a>' 445*4882a593Smuzhiyun 446*4882a593Smuzhiyun def task_link_tmpl(val): 447*4882a593Smuzhiyun return ('<a name="task-{{data.order}}"' 448*4882a593Smuzhiyun 'href="{%% url "task" extra.build.pk data.pk %%}">' 449*4882a593Smuzhiyun '%s' 450*4882a593Smuzhiyun '</a>') % str(val) 451*4882a593Smuzhiyun 452*4882a593Smuzhiyun self.add_column(title="Order", 453*4882a593Smuzhiyun static_data_name="order", 454*4882a593Smuzhiyun static_data_template='{{data.order}}', 455*4882a593Smuzhiyun hideable=False, 456*4882a593Smuzhiyun orderable=True) 457*4882a593Smuzhiyun 458*4882a593Smuzhiyun self.add_column(title="Task", 459*4882a593Smuzhiyun static_data_name="task_name", 460*4882a593Smuzhiyun static_data_template=task_link_tmpl( 461*4882a593Smuzhiyun "{{data.task_name}}"), 462*4882a593Smuzhiyun hideable=False, 463*4882a593Smuzhiyun orderable=True) 464*4882a593Smuzhiyun 465*4882a593Smuzhiyun self.add_column(title="Recipe", 466*4882a593Smuzhiyun static_data_name='recipe__name', 467*4882a593Smuzhiyun static_data_template=recipe_name_tmpl, 468*4882a593Smuzhiyun hideable=False, 469*4882a593Smuzhiyun orderable=True) 470*4882a593Smuzhiyun 471*4882a593Smuzhiyun self.add_column(title="Recipe version", 472*4882a593Smuzhiyun field_name='recipe__version', 473*4882a593Smuzhiyun hidden=True) 474*4882a593Smuzhiyun 475*4882a593Smuzhiyun self.add_column(title="Executed", 476*4882a593Smuzhiyun static_data_name="task_executed", 477*4882a593Smuzhiyun static_data_template='{{data.get_executed_display}}', 478*4882a593Smuzhiyun filter_name='execution_outcome', 479*4882a593Smuzhiyun orderable=True) 480*4882a593Smuzhiyun 481*4882a593Smuzhiyun self.static_context_extra['OUTCOME_FAILED'] = Task.OUTCOME_FAILED 482*4882a593Smuzhiyun outcome_tmpl = '{{data.outcome_text}}' 483*4882a593Smuzhiyun outcome_tmpl = ('%s ' 484*4882a593Smuzhiyun '{%% if data.outcome = extra.OUTCOME_FAILED %%}' 485*4882a593Smuzhiyun '<a href="{%% url "build_artifact" extra.build.pk ' 486*4882a593Smuzhiyun ' "tasklogfile" data.pk %%}">' 487*4882a593Smuzhiyun ' <span class="glyphicon glyphicon-download-alt' 488*4882a593Smuzhiyun ' get-help" title="Download task log file"></span>' 489*4882a593Smuzhiyun '</a> {%% endif %%}' 490*4882a593Smuzhiyun '<span class="glyphicon glyphicon-question-sign' 491*4882a593Smuzhiyun ' get-help hover-help" style="visibility: hidden;" ' 492*4882a593Smuzhiyun 'title="{{data.get_outcome_help}}"></span>' 493*4882a593Smuzhiyun ) % outcome_tmpl 494*4882a593Smuzhiyun 495*4882a593Smuzhiyun self.add_column(title="Outcome", 496*4882a593Smuzhiyun static_data_name="outcome", 497*4882a593Smuzhiyun static_data_template=outcome_tmpl, 498*4882a593Smuzhiyun filter_name="task_outcome", 499*4882a593Smuzhiyun orderable=True) 500*4882a593Smuzhiyun 501*4882a593Smuzhiyun self.toggle_columns['sstate_result'] = len(self.columns) 502*4882a593Smuzhiyun 503*4882a593Smuzhiyun self.add_column(title="Cache attempt", 504*4882a593Smuzhiyun static_data_name="sstate_result", 505*4882a593Smuzhiyun static_data_template='{{data.sstate_text}}', 506*4882a593Smuzhiyun filter_name="sstate_outcome", 507*4882a593Smuzhiyun orderable=True) 508*4882a593Smuzhiyun 509*4882a593Smuzhiyun self.toggle_columns['elapsed_time'] = len(self.columns) 510*4882a593Smuzhiyun 511*4882a593Smuzhiyun self.add_column( 512*4882a593Smuzhiyun title="Time (secs)", 513*4882a593Smuzhiyun static_data_name="elapsed_time", 514*4882a593Smuzhiyun static_data_template='{% load projecttags %}{% load humanize %}' 515*4882a593Smuzhiyun '{{data.elapsed_time|format_none_and_zero|floatformat:2}}', 516*4882a593Smuzhiyun orderable=True, 517*4882a593Smuzhiyun hidden=True) 518*4882a593Smuzhiyun 519*4882a593Smuzhiyun self.toggle_columns['cpu_time_sys'] = len(self.columns) 520*4882a593Smuzhiyun 521*4882a593Smuzhiyun self.add_column( 522*4882a593Smuzhiyun title="System CPU time (secs)", 523*4882a593Smuzhiyun static_data_name="cpu_time_system", 524*4882a593Smuzhiyun static_data_template='{% load projecttags %}{% load humanize %}' 525*4882a593Smuzhiyun '{{data.cpu_time_system|format_none_and_zero|floatformat:2}}', 526*4882a593Smuzhiyun hidden=True, 527*4882a593Smuzhiyun orderable=True) 528*4882a593Smuzhiyun 529*4882a593Smuzhiyun self.toggle_columns['cpu_time_user'] = len(self.columns) 530*4882a593Smuzhiyun 531*4882a593Smuzhiyun self.add_column( 532*4882a593Smuzhiyun title="User CPU time (secs)", 533*4882a593Smuzhiyun static_data_name="cpu_time_user", 534*4882a593Smuzhiyun static_data_template='{% load projecttags %}{% load humanize %}' 535*4882a593Smuzhiyun '{{data.cpu_time_user|format_none_and_zero|floatformat:2}}', 536*4882a593Smuzhiyun hidden=True, 537*4882a593Smuzhiyun orderable=True) 538*4882a593Smuzhiyun 539*4882a593Smuzhiyun self.toggle_columns['disk_io'] = len(self.columns) 540*4882a593Smuzhiyun 541*4882a593Smuzhiyun self.add_column( 542*4882a593Smuzhiyun title="Disk I/O (ms)", 543*4882a593Smuzhiyun static_data_name="disk_io", 544*4882a593Smuzhiyun static_data_template='{% load projecttags %}{% load humanize %}' 545*4882a593Smuzhiyun '{{data.disk_io|format_none_and_zero|filtered_filesizeformat}}', 546*4882a593Smuzhiyun hidden=True, 547*4882a593Smuzhiyun orderable=True) 548*4882a593Smuzhiyun 549*4882a593Smuzhiyun 550*4882a593Smuzhiyunclass BuildTimeTable(BuildTasksTable): 551*4882a593Smuzhiyun """ Same as tasks table but the Time column is default displayed""" 552*4882a593Smuzhiyun 553*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 554*4882a593Smuzhiyun super(BuildTimeTable, self).__init__(*args, **kwargs) 555*4882a593Smuzhiyun self.default_orderby = "-elapsed_time" 556*4882a593Smuzhiyun 557*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 558*4882a593Smuzhiyun super(BuildTimeTable, self).setup_columns(**kwargs) 559*4882a593Smuzhiyun 560*4882a593Smuzhiyun self.columns[self.toggle_columns['order']]['hidden'] = True 561*4882a593Smuzhiyun self.columns[self.toggle_columns['order']]['hideable'] = True 562*4882a593Smuzhiyun self.columns[self.toggle_columns['sstate_result']]['hidden'] = True 563*4882a593Smuzhiyun self.columns[self.toggle_columns['elapsed_time']]['hidden'] = False 564*4882a593Smuzhiyun 565*4882a593Smuzhiyun 566*4882a593Smuzhiyunclass BuildCPUTimeTable(BuildTasksTable): 567*4882a593Smuzhiyun """ Same as tasks table but the CPU usage columns are default displayed""" 568*4882a593Smuzhiyun 569*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 570*4882a593Smuzhiyun super(BuildCPUTimeTable, self).__init__(*args, **kwargs) 571*4882a593Smuzhiyun self.default_orderby = "-cpu_time_system" 572*4882a593Smuzhiyun 573*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 574*4882a593Smuzhiyun super(BuildCPUTimeTable, self).setup_columns(**kwargs) 575*4882a593Smuzhiyun 576*4882a593Smuzhiyun self.columns[self.toggle_columns['order']]['hidden'] = True 577*4882a593Smuzhiyun self.columns[self.toggle_columns['order']]['hideable'] = True 578*4882a593Smuzhiyun self.columns[self.toggle_columns['sstate_result']]['hidden'] = True 579*4882a593Smuzhiyun self.columns[self.toggle_columns['cpu_time_sys']]['hidden'] = False 580*4882a593Smuzhiyun self.columns[self.toggle_columns['cpu_time_user']]['hidden'] = False 581*4882a593Smuzhiyun 582*4882a593Smuzhiyun 583*4882a593Smuzhiyunclass BuildIOTable(BuildTasksTable): 584*4882a593Smuzhiyun """ Same as tasks table but the Disk IO column is default displayed""" 585*4882a593Smuzhiyun 586*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 587*4882a593Smuzhiyun super(BuildIOTable, self).__init__(*args, **kwargs) 588*4882a593Smuzhiyun self.default_orderby = "-disk_io" 589*4882a593Smuzhiyun 590*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 591*4882a593Smuzhiyun super(BuildIOTable, self).setup_columns(**kwargs) 592*4882a593Smuzhiyun 593*4882a593Smuzhiyun self.columns[self.toggle_columns['order']]['hidden'] = True 594*4882a593Smuzhiyun self.columns[self.toggle_columns['order']]['hideable'] = True 595*4882a593Smuzhiyun self.columns[self.toggle_columns['sstate_result']]['hidden'] = True 596*4882a593Smuzhiyun self.columns[self.toggle_columns['disk_io']]['hidden'] = False 597