[yocto] [layerindex-web][PATCH 3/5] Improve collection/display of LayerUpdate records

Paul Eggleton paul.eggleton at linux.intel.com
Tue Aug 14 06:32:16 PDT 2018


Make layerupdate collection slightly more reliable and make it easier
to see when updates have actually been captured:

* Split layerbranch into separate layer and branch fields, since there
  may not be a layerbranch in existence but we might want to log an
  error relating to the branch and layer.
* Show all layerupdates on the update detail page, not just those with
  log messages
* Record before and after revisions and show these in the update detail
  and layerupdate detail (with links)
* Record return code of update_layer process
* Highlight layer updates with a non-zero return code, errors or
  warnings in the output on the update detail page
* Show duration on the layerupdate detail page

Signed-off-by: Paul Eggleton <paul.eggleton at linux.intel.com>
---
 TODO                                          |  1 -
 layerindex/admin.py                           |  2 +-
 .../0021_layerupdate_add_layer_branch.py      | 26 +++++++++++
 .../0022_layerupdate_set_layer_branch.py      | 41 +++++++++++++++++
 .../0023_layerupdate_layer_branch_finalise.py | 29 ++++++++++++
 .../migrations/0024_layerupdate_vcs_revs.py   | 35 +++++++++++++++
 layerindex/models.py                          | 28 ++++++++++--
 layerindex/static/css/additional.css          | 10 +++++
 layerindex/templatetags/extrafilters.py       |  4 ++
 layerindex/update.py                          | 44 ++++++++++++-------
 layerindex/views.py                           |  4 +-
 templates/layerindex/layerupdate.html         | 19 +++++++-
 templates/layerindex/updatedetail.html        | 34 ++++++++++++--
 13 files changed, 249 insertions(+), 28 deletions(-)
 create mode 100644 layerindex/migrations/0021_layerupdate_add_layer_branch.py
 create mode 100644 layerindex/migrations/0022_layerupdate_set_layer_branch.py
 create mode 100644 layerindex/migrations/0023_layerupdate_layer_branch_finalise.py
 create mode 100644 layerindex/migrations/0024_layerupdate_vcs_revs.py

diff --git a/TODO b/TODO
index aab3a4eb..ae2ab07f 100644
--- a/TODO
+++ b/TODO
@@ -43,7 +43,6 @@ Features
 * Use bar instead of pie graphs for OE-Classic statistics
 * Ability for reviewers to comment before publishing a layer?
 * Show a note at the top of the layer edit form if there's a validation error
-* Record layer update start/end revisions
 * Show count in duplicates page
 * Search on layer selection dialogs
 
diff --git a/layerindex/admin.py b/layerindex/admin.py
index a7c62783..bd9c66b1 100644
--- a/layerindex/admin.py
+++ b/layerindex/admin.py
@@ -85,7 +85,7 @@ class UpdateAdmin(admin.ModelAdmin):
     pass
 
 class LayerUpdateAdmin(admin.ModelAdmin):
-    list_filter = ['update__started', 'layerbranch__layer__name', 'layerbranch__branch__name']
+    list_filter = ['update__started', 'layer', 'branch']
 
 class RecipeAdmin(admin.ModelAdmin):
     search_fields = ['filename', 'pn']
diff --git a/layerindex/migrations/0021_layerupdate_add_layer_branch.py b/layerindex/migrations/0021_layerupdate_add_layer_branch.py
new file mode 100644
index 00000000..573c7cb1
--- /dev/null
+++ b/layerindex/migrations/0021_layerupdate_add_layer_branch.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.12 on 2018-08-13 22:44
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('layerindex', '0020_update_manual'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='layerupdate',
+            name='branch',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='layerindex.Branch'),
+        ),
+        migrations.AddField(
+            model_name='layerupdate',
+            name='layer',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='layerindex.LayerItem'),
+        ),
+    ]
diff --git a/layerindex/migrations/0022_layerupdate_set_layer_branch.py b/layerindex/migrations/0022_layerupdate_set_layer_branch.py
new file mode 100644
index 00000000..e0e47291
--- /dev/null
+++ b/layerindex/migrations/0022_layerupdate_set_layer_branch.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.12 on 2018-08-13 23:05
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def set_branch_layer(apps, schema_editor):
+    LayerUpdate = apps.get_model('layerindex', 'LayerUpdate')
+    for layerupdate in LayerUpdate.objects.all():
+        layerupdate.branch = layerupdate.layerbranch.branch
+        layerupdate.layer = layerupdate.layerbranch.layer
+        layerupdate.save()
+
+def set_layerbranch(apps, schema_editor):
+    LayerUpdate = apps.get_model('layerindex', 'LayerUpdate')
+    LayerBranch = apps.get_model('layerindex', 'LayerBranch')
+    to_delete = []
+    for layerupdate in LayerUpdate.objects.all():
+        layerbranch = LayerBranch.objects.filter(layeritem=layerupdate.layer, branch=layerupdate.branch).first()
+        if layerbranch:
+            layerupdate.layerbranch = layerbranch
+            layerupdate.save()
+        else:
+            # The whole point of splitting layerbranch -> layer,branch was to
+            # be able to have records with no corresponding LayerBranch, so we
+            # now have to delete any that don't when reversing
+            to_delete.append(layerupdate.id)
+    for luid in to_delete:
+        LayerUpdate.objects.filter(id=luid).delete()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('layerindex', '0021_layerupdate_add_layer_branch'),
+    ]
+
+    operations = [
+        migrations.RunPython(set_branch_layer, reverse_code=set_layerbranch),
+    ]
diff --git a/layerindex/migrations/0023_layerupdate_layer_branch_finalise.py b/layerindex/migrations/0023_layerupdate_layer_branch_finalise.py
new file mode 100644
index 00000000..ba6b72f2
--- /dev/null
+++ b/layerindex/migrations/0023_layerupdate_layer_branch_finalise.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.12 on 2018-08-13 23:06
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('layerindex', '0022_layerupdate_set_layer_branch'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='layerupdate',
+            name='branch',
+            field=models.ForeignKey(to='layerindex.Branch'),
+        ),
+        migrations.AlterField(
+            model_name='layerupdate',
+            name='layer',
+            field=models.ForeignKey(to='layerindex.LayerItem'),
+        ),
+        migrations.RemoveField(
+            model_name='layerupdate',
+            name='layerbranch',
+        ),
+    ]
diff --git a/layerindex/migrations/0024_layerupdate_vcs_revs.py b/layerindex/migrations/0024_layerupdate_vcs_revs.py
new file mode 100644
index 00000000..0d69043e
--- /dev/null
+++ b/layerindex/migrations/0024_layerupdate_vcs_revs.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.12 on 2018-08-13 23:24
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('layerindex', '0023_layerupdate_layer_branch_finalise'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='layerupdate',
+            name='vcs_after_rev',
+            field=models.CharField(blank=True, max_length=80, verbose_name='Revision after'),
+        ),
+        migrations.AddField(
+            model_name='layerupdate',
+            name='vcs_before_rev',
+            field=models.CharField(blank=True, max_length=80, verbose_name='Revision before'),
+        ),
+        migrations.AddField(
+            model_name='layerupdate',
+            name='retcode',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AlterField(
+            model_name='layerupdate',
+            name='finished',
+            field=models.DateTimeField(blank=True, null=True),
+        ),
+    ]
diff --git a/layerindex/models.py b/layerindex/models.py
index 6042d58d..0dc4bd7c 100644
--- a/layerindex/models.py
+++ b/layerindex/models.py
@@ -394,13 +394,35 @@ class LayerNote(models.Model):
 
 
 class LayerUpdate(models.Model):
-    layerbranch = models.ForeignKey(LayerBranch)
+    layer = models.ForeignKey(LayerItem)
+    branch = models.ForeignKey(Branch)
     update = models.ForeignKey(Update)
     started = models.DateTimeField()
-    finished = models.DateTimeField()
+    finished = models.DateTimeField(blank=True, null=True)
     errors = models.IntegerField(default=0)
     warnings = models.IntegerField(default=0)
+    vcs_before_rev = models.CharField('Revision before', max_length=80, blank=True)
+    vcs_after_rev = models.CharField('Revision after', max_length=80, blank=True)
     log = models.TextField(blank=True)
+    retcode = models.IntegerField(default=0)
+
+    def layerbranch_exists(self):
+        """Helper function for linking"""
+        return LayerBranch.objects.filter(layer=self.layer, branch=self.branch).exists()
+
+    def vcs_before_commit_url(self):
+        if self.vcs_before_rev:
+            layerbranch = LayerBranch.objects.filter(layer=self.layer, branch=self.branch).first()
+            if layerbranch:
+                return layerbranch.commit_url(self.vcs_before_rev)
+        return None
+
+    def vcs_after_commit_url(self):
+        if self.vcs_after_rev:
+            layerbranch = LayerBranch.objects.filter(layer=self.layer, branch=self.branch).first()
+            if layerbranch:
+                return layerbranch.commit_url(self.vcs_after_rev)
+        return None
 
     def save(self):
         warnings = 0
@@ -415,7 +437,7 @@ class LayerUpdate(models.Model):
         super(LayerUpdate, self).save()
 
     def __str__(self):
-        return "%s: %s: %s" % (self.layerbranch.layer.name, self.layerbranch.branch.name, self.started)
+        return "%s: %s: %s" % (self.layer.name, self.branch.name, self.started)
 
 
 class Recipe(models.Model):
diff --git a/layerindex/static/css/additional.css b/layerindex/static/css/additional.css
index 30d2b77d..28af9bc9 100644
--- a/layerindex/static/css/additional.css
+++ b/layerindex/static/css/additional.css
@@ -258,3 +258,13 @@ td.info {
 .hidden-select {
     display: none !important;
 }
+
+.pre-plain {
+    background-color: transparent;
+    border-style: none;
+    padding: 0;
+}
+
+.td-pre {
+    background-color: #f5f5f5;
+}
diff --git a/layerindex/templatetags/extrafilters.py b/layerindex/templatetags/extrafilters.py
index 852e426b..c061a945 100644
--- a/layerindex/templatetags/extrafilters.py
+++ b/layerindex/templatetags/extrafilters.py
@@ -12,6 +12,10 @@ def replace_commas(string):
 def squashspaces(strval):
     return utils.squashspaces(strval)
 
+ at register.filter
+def truncatesimple(strval, length):
+    return strval[:length]
+
 @register.filter
 def timesince2(date, date2=None):
     # Based on http://www.didfinishlaunchingwithoptions.com/a-better-timesince-template-filter-for-django/
diff --git a/layerindex/update.py b/layerindex/update.py
index 5713b2e5..14529498 100755
--- a/layerindex/update.py
+++ b/layerindex/update.py
@@ -303,7 +303,6 @@ def main():
             # We now do this by calling out to a separate script; doing otherwise turned out to be
             # unreliable due to leaking memory (we're using bitbake internals in a manner in which
             # they never get used during normal operation).
-            last_rev = {}
             failed_layers = {}
             for branch in branches:
                 failed_layers[branch] = []
@@ -401,6 +400,17 @@ def main():
                         logger.info('Update interrupted, exiting')
                         sys.exit(254)
                     elif ret != 0:
+                        output = output.rstrip()
+                        # Save a layerupdate here or we won't see this output
+                        layerupdate = LayerUpdate()
+                        layerupdate.update = update
+                        layerupdate.layer = layer
+                        layerupdate.branch = branchobj
+                        layerupdate.started = datetime.now()
+                        layerupdate.log = output
+                        layerupdate.retcode = ret
+                        if not options.dryrun:
+                            layerupdate.save()
                         continue
 
                     col = extract_value('BBFILE_COLLECTIONS', output)
@@ -479,23 +489,27 @@ def main():
                 for layer in layerquery_sorted:
                     layerupdate = LayerUpdate()
                     layerupdate.update = update
+                    layerupdate.layer = layer
+                    layerupdate.branch = branchobj
+                    layerbranch = layer.get_layerbranch(branch)
+                    if layerbranch:
+                        layerupdate.vcs_before_rev = layerbranch.vcs_last_rev
 
                     errmsg = failedrepos.get(layer.vcs_url, '')
                     if errmsg:
                         logger.info("Skipping update of layer %s as fetch of repository %s failed:\n%s" % (layer.name, layer.vcs_url, errmsg))
-                        layerbranch = layer.get_layerbranch(branch)
-                        if layerbranch:
-                            layerupdate.layerbranch = layerbranch
-                            layerupdate.started = datetime.now()
-                            layerupdate.finished = datetime.now()
-                            layerupdate.log = 'ERROR: fetch failed: %s' % errmsg
-                            if not options.dryrun:
-                                layerupdate.save()
+                        layerupdate.started = datetime.now()
+                        layerupdate.finished = datetime.now()
+                        layerupdate.log = 'ERROR: fetch failed: %s' % errmsg
+                        if not options.dryrun:
+                            layerupdate.save()
                         continue
 
+                    layerupdate.started = datetime.now()
+                    if not options.dryrun:
+                        layerupdate.save()
                     cmd = prepare_update_layer_command(options, branchobj, layer)
                     logger.debug('Running layer update command: %s' % cmd)
-                    layerupdate.started = datetime.now()
                     ret, output = utils.run_command_interruptible(cmd)
                     layerupdate.finished = datetime.now()
 
@@ -504,11 +518,11 @@ def main():
                     # didn't exist) so we still need to check
                     layerbranch = layer.get_layerbranch(branch)
                     if layerbranch:
-                        last_rev[layerbranch] = layerbranch.vcs_last_rev
-                        layerupdate.layerbranch = layerbranch
-                        layerupdate.log = output
-                        if not options.dryrun:
-                            layerupdate.save()
+                        layerupdate.vcs_after_rev = layerbranch.vcs_last_rev
+                    layerupdate.log = output
+                    layerupdate.retcode = ret
+                    if not options.dryrun:
+                        layerupdate.save()
 
                     if ret == 254:
                         # Interrupted by user, break out of loop
diff --git a/layerindex/views.py b/layerindex/views.py
index fb2dd51a..69165c48 100644
--- a/layerindex/views.py
+++ b/layerindex/views.py
@@ -373,7 +373,7 @@ class LayerDetailView(DetailView):
             context['distros'] = layerbranch.distro_set.order_by('name')
             context['appends'] = layerbranch.bbappend_set.order_by('filename')
             context['classes'] = layerbranch.bbclass_set.order_by('name')
-            context['updates'] = layerbranch.layerupdate_set.order_by('-started')
+            context['updates'] = LayerUpdate.objects.filter(layer=layerbranch.layer, branch=layerbranch.branch).order_by('-started')
         context['url_branch'] = self.kwargs['branch']
         context['this_url_name'] = resolve(self.request.path_info).url_name
         if 'rrs' in settings.INSTALLED_APPS:
@@ -718,7 +718,7 @@ class UpdateDetailView(DetailView):
         context = super(UpdateDetailView, self).get_context_data(**kwargs)
         update = self.get_object()
         if update:
-            context['layerupdates'] = update.layerupdate_set.exclude(log__isnull=True).exclude(log__exact='')
+            context['layerupdates'] = update.layerupdate_set.order_by('-started')
         return context
 
 
diff --git a/templates/layerindex/layerupdate.html b/templates/layerindex/layerupdate.html
index d969fc66..1cd01039 100644
--- a/templates/layerindex/layerupdate.html
+++ b/templates/layerindex/layerupdate.html
@@ -1,5 +1,6 @@
 {% extends "base.html" %}
 {% load i18n %}
+{% load extrafilters %}
 
 {% comment %}
 
@@ -11,13 +12,27 @@
 {% endcomment %}
 
 <!--
-{% block title_append %} - {{ layerupdate.layerbranch.layer.name }} {{ layerupdate.layerbranch.branch.name }} - {{ layerupdate.started }} {% endblock %}
+{% block title_append %} - {{ layerupdate.layer.name }} {{ layerupdate.branch.name }} - {{ layerupdate.started }} {% endblock %}
 -->
 
 {% block content %}
 {% autoescape on %}
 
-<h2>{{ layerupdate.layerbranch.layer.name }} {{ layerupdate.layerbranch.branch.name }} - {{ layerupdate.started }}</h2>
+<h2>{{ layerupdate.layer.name }} {{ layerupdate.branch.name }} - {{ layerupdate.started }} <small>({{ layerupdate.started|timesince2:layerupdate.finished }})</small></h2>
+
+{% if layerupdate.layerbranch_exists %}
+{% if layerupdate.vcs_before_rev != layerupdate.vcs_after_rev %}
+<p>
+    Updated
+    {% with before_url=layerupdate.vcs_before_commit_url after_url=layerupdate.vcs_after_commit_url %}
+    {% if before_url %}<a href="{{ before_url }}">{% endif %}{{ layerupdate.vcs_before_rev|truncatesimple:10 }}{% if before_url %}</a>{% endif %} → {% if after_url %}<a href="{{ after_url }}">{% endif %}{{ layerupdate.vcs_after_rev|truncatesimple:10 }}{% if after_url %}</a>{% endif %}
+    {% endwith %}
+    {% else %}
+    {{ layerupdate.vcs_before_rev|truncatesimple:10 }} → {{ layerupdate.vcs_after_rev|truncatesimple:10 }}
+</p>
+{% endif %}
+{% endif %}
+
 
 <pre>{{ layerupdate.log }}</pre>
 
diff --git a/templates/layerindex/updatedetail.html b/templates/layerindex/updatedetail.html
index 59723fe2..a1ce47cc 100644
--- a/templates/layerindex/updatedetail.html
+++ b/templates/layerindex/updatedetail.html
@@ -1,11 +1,12 @@
 {% extends "base.html" %}
 {% load i18n %}
+{% load extrafilters %}
 
 {% comment %}
 
   layerindex-web - update page
 
-  Copyright (C) 2016 Intel Corporation
+  Copyright (C) 2016, 2018 Intel Corporation
   Licensed under the MIT license, see COPYING.MIT for details
 
 {% endcomment %}
@@ -30,12 +31,37 @@
 {% endif %}
 
 {% for layerupdate in layerupdates %}
-    <a href="{% url 'layer_item' layerupdate.layerbranch.branch.name layerupdate.layerbranch.layer.name %}"><h3>{{ layerupdate.layerbranch.layer.name }} {{ layerupdate.layerbranch.branch.name }}</h3></a>
-    <pre>{{ layerupdate.log }}</pre>
+    <table class="table table-bordered">
+        <thead>
+            {% with layerbranch_exists=layerupdate.layerbranch_exists %}
+            <tr><td{% if layerupdate.errors > 0 or layerupdate.retcode != 0 %} class="error"{% elif layerupdate.warnings %} class="warning"{% endif %}>
+            {% if layerbranch_exists %}<a href="{% url 'layer_item' layerupdate.branch.name layerupdate.layer.name %}">{% endif %}<strong>{{ layerupdate.layer.name }} {{ layerupdate.branch.name }}</strong>{% if layerbranch_exists %}</a>{% endif %}
+            {% if layerupdate.vcs_before_rev != layerupdate.vcs_after_rev %}
+            <span class="pull-right">
+                {% if layerbranch_exists %}
+                {% with before_url=layerupdate.vcs_before_commit_url after_url=layerupdate.vcs_after_commit_url %}
+                {% if before_url %}<a href="{{ before_url }}">{% endif %}{{ layerupdate.vcs_before_rev|truncatesimple:10 }}{% if before_url %}</a>{% endif %} → {% if after_url %}<a href="{{ after_url }}">{% endif %}{{ layerupdate.vcs_after_rev|truncatesimple:10 }}{% if after_url %}</a>{% endif %}
+                {% endwith %}
+                {% else %}
+                {{ layerupdate.vcs_before_rev|truncatesimple:10 }} → {{ layerupdate.vcs_after_rev|truncatesimple:10 }}
+                {% endif %}
+            </span>
+            {% endif %}
+            </td></tr>
+            {% endwith %}
+        </thead>
+        <tbody>
+            {% if layerupdate.log %}
+            <tr><td class="td-pre">
+            <pre class="pre-scrollable pre-plain">{{ layerupdate.log }}</pre>
+            </td></tr>
+            {% endif %}
+        </tbody>
+    </table>
 {% endfor %}
 
 {% if not update.log and not layerupdates %}
-    <p>No messages</p>
+    <p>No messages or layer updates</p>
 {% endif %}
 
 {% if update.comparisonrecipeupdate_set.exists %}
-- 
2.17.1



More information about the yocto mailing list