layerindex: add Update Layer UI feature

* Add an "Update Layer" button to the layer detail view.
  - This allows a user that is a member of is_staff to trigger
    an update of the current layer (for the current branch)
* Add an "Update Layer" button to the reviewdetail view
  - This allows a user that is a member of is_staff and has
    publish_layer permissions to trigger an update attempt
    of the layer under review (even in the un-published state)
* The update is run as a task with Celery

NOTE:
  You must have the RABBITMQ_ and DATABASE_ credentials set
  correctly in the docker/settings.py file or set via
  environment variables or you will get authentication errors
  talking to layersdb or layersrabbit containers.

[YOCTO #12484]

layerindex/views.py: add update_layer_view
layerindex/urls.py: add update_layer_view
layerindex/urls_branch.py: add update_layer_view
templates/layerindex/reviewdetail.html: add Update Layer button
templates/layerindex/detail.html: add Update Layer button
templates/layerindex: add updatelayer.html

TODO:
  While the update is happening, the AJAX rendering of the
  update.log is showing the b'' characters and not adding
  any new lines. If you go back to the same task view
  afterwards, the log is rendered as expected.

TODO:
  After the update is completed, it would be nice to have a
  button to return you to the page from where you called the
  "Update Layer".

Signed-off-by: Tim Orling <tim.orling@konsulko.com>
This commit is contained in:
Tim Orling 2023-12-27 19:14:28 -08:00
parent 083c234764
commit bf57322ab1
6 changed files with 163 additions and 2 deletions

View File

@ -16,7 +16,7 @@ from layerindex.views import LayerListView, LayerReviewListView, LayerReviewDeta
ClassicRecipeSearchView, ClassicRecipeDetailView, ClassicRecipeStatsView, LayerUpdateDetailView, UpdateListView, \
UpdateDetailView, StatsView, publish_view, LayerCheckListView, BBClassCheckListView, TaskStatusView, \
ComparisonRecipeSelectView, ComparisonRecipeSelectDetailView, task_log_view, task_stop_view, email_test_view, \
BranchCompareView, RecipeDependenciesView
BranchCompareView, RecipeDependenciesView, update_layer_view
from layerindex.models import LayerItem, Recipe, RecipeChangeset
from rest_framework import routers
from . import restviews

View File

@ -8,7 +8,7 @@
from django.views.defaults import page_not_found
from django.urls import include, re_path, reverse_lazy
from layerindex.views import LayerListView, RecipeSearchView, MachineSearchView, DistroSearchView, ClassSearchView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, RedirectParamsView, DuplicatesView, LayerUpdateDetailView, layer_export_recipes_csv_view, comparison_update_view
from layerindex.views import LayerListView, RecipeSearchView, MachineSearchView, DistroSearchView, ClassSearchView, LayerDetailView, edit_layer_view, delete_layer_view, edit_layernote_view, delete_layernote_view, RedirectParamsView, DuplicatesView, LayerUpdateDetailView, layer_export_recipes_csv_view, comparison_update_view, update_layer_view
urlpatterns = [
re_path(r'^$',
@ -41,6 +41,7 @@ urlpatterns = [
template_name='layerindex/classes.html'),
name='class_search'),
re_path(r'^edit/(?P<slug>[-\w]+)/$', edit_layer_view, {'template_name': 'layerindex/editlayer.html'}, name="edit_layer"),
re_path(r'^update/(?P<slug>[-\w]+)/$', update_layer_view, {'template_name': 'layerindex/updatelayer.html'}, name="update_layer"),
re_path(r'^duplicates/$',
DuplicatesView.as_view(
template_name='layerindex/duplicates.html'),

View File

@ -121,6 +121,41 @@ def delete_layer_view(request, template_name, slug):
'cancel_url': layeritem.get_absolute_url()
})
def update_layer_view(request, template_name, branch='master', slug=None):
if not (request.user.is_authenticated and request.user.is_staff):
raise PermissionDenied
return_url = None
branchobj = Branch.objects.filter(name=branch)[:1].get()
if slug:
# Existing Layer Update
layeritem = get_object_or_404(LayerItem, name=slug)
layerbranch = get_object_or_404(LayerBranch, layer=layeritem, branch=branchobj)
returnto = request.GET.get('returnto', 'layer_item')
if returnto:
if returnto == 'layer_review':
return_url = reverse_lazy(returnto, args=(layeritem.name,))
else:
return_url = reverse_lazy(returnto, args=(branch, layeritem.name))
else:
# Pre-Publish Layer Update attempt
layeritem = LayerItem()
layerbranch = LayerBranch(layer=layeritem, branch=branchobj)
from celery import uuid
cmd = 'layerindex/update.py --layer %s --branch %s;' % (layeritem.name, branch)
task_id = uuid()
# Create this here first, because inside the task we don't have all of the required info
update = Update(task_id=task_id)
update.started = datetime.now()
update.triggered_by = request.user
update.save()
res = tasks.run_update_command.apply_async((branch, cmd), task_id=task_id)
return HttpResponseRedirect(reverse_lazy('task_status', kwargs={'task_id': task_id}))
def edit_layer_view(request, template_name, branch='master', slug=None):
return_url = None
branchobj = Branch.objects.filter(name=branch)[:1].get()

View File

@ -38,6 +38,9 @@
{% endif %}
{% if user.is_authenticated %}
<span class="pull-right">
{% if user.is_staff %}
<a href="{% url 'update_layer' url_branch layeritem.name %}" class="btn btn-info">Update layer</a>
{% endif %}
{% if perms.layerindex.publish_layer or useredit %}
<a href="{% url 'edit_layer' url_branch layeritem.name %}" class="btn btn-default">Edit layer</a>
{% if layeritem.layernote_set.count == 0 %}

View File

@ -47,6 +47,9 @@
<a href="{% url 'delete_layer' layeritem.name %}" class="btn btn-warning">Delete layer</a>
<a href="{% url 'publish' layeritem.name %}" class="btn btn-primary">Publish layer</a>
{% endif %}
{% if user.is_staff %}
<a href="{% url 'update_layer' 'master' layeritem.name %}" class="btn btn-info">Update layer</a>
{% endif %}
</span>
{% endif %}
</h1>

View File

@ -0,0 +1,119 @@
{% extends "base.html" %}
{% load i18n %}
{% load extrafilters %}
{% comment %}
layerindex-web - update page
Copyright (C) 2016, 2018 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% block title_append %} - {{ update.started }} {% endblock %}
-->
{% block content %}
{% autoescape on %}
<ul class="breadcrumb">
<li><a href="{% url 'update_list' %}">Updates</a></li>
<li class="active">{{ update.started }}</li>
</ul>
<h2>{{ update.started }} {% if update.reload %}(reload){% endif %}
<span id="status-label" class="label {% if update.finished %}{% if update.retcode %}label-danger{% else %}label-success{% endif %}{% endif %} pull-right">{% if update.finished %}{% if update.retcode < 0 %}TERMINATED ({{ update.retcode }}){% elif update.retcode %}FAILED{% endif %}{% endif %}</span>
</h2>
{% if update.log %}
<pre>{{ update.log }}</pre>
{% endif %}
{% for layerupdate in layerupdates %}
<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 %} &rarr; {% 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 or layer updates</p>
{% endif %}
{% if update.comparisonrecipeupdate_set.exists %}
<h3>Updated comparison recipes</h3>
<ul>
{% for recipeupdate in update.comparisonrecipeupdate_set.all %}
<li><a href="{% url 'comparison_recipe' recipeupdate.recipe.id %}">{{ recipeupdate.recipe.pn }}</a> {% if recipeupdate.meta_updated and recipeupdate.link_updated %}(meta, link){% elif recipeupdate.link_updated %}(link){% elif recipeupdate.meta_updated %}(meta){% endif %}</li>
{% endfor %}
</ul>
{% endif %}
{% endautoescape %}
{% endblock %}
{% if updates.count > 0 %}
<div class="tab-pane" id="updates">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand">{{ layeritem.name }} updates</a>
</div>
</div>
</nav>
<table class="table table-bordered">
<thead>
<tr>
<th>Date/time</th>
<th>Errors</th>
<th>Warnings</th>
</tr>
</thead>
<tbody>
{% for update in updates %}
<tr>
<td>
{% if update.log %}
<a href="{% url 'layerupdate' update.id %}">{{ update.started }}{% if update.update.reload %} (reload){% endif%}</a>
{% else %}
<span class="text-muted">{{ update.started }}{% if update.update.reload %} (reload){% endif%}</span>
{% endif %}
</td>
<td>{% if update.errors %}<span class="badge badge-important">{{ update.errors }}</span>{% endif %}</td>
<td>{% if update.warnings %}<span class="badge badge-warning">{{ update.warnings }}</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% block scripts %}
{% endblock %}