1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00

fix(operations): Jobs dates carry the year and time zone, filters match part of a name

Job dates showed no year and no time zone, unlike the Logs page; they now
read 2026-10-13 23:04:00 under "Created (time zone)". The host filter
matched only a whole host name and the user filter only an exact username;
both now ignore case and take the start of a host or any part of a name.
This commit is contained in:
catborise 2026-10-26 10:51:05 +03:00
parent d80ec72fe0
commit f7279610a8
4 changed files with 27 additions and 12 deletions

View file

@ -2,6 +2,7 @@
{% load i18n %}
{% load bootstrap_icons %}
{% load operations %}
{% load tz %}
{% block title %}{{ job.label }}{% endblock %}
@ -45,9 +46,10 @@
</span>
</dd>
{% endif %}
<dt class="col-sm-3">{% trans "Created" %}</dt><dd class="col-sm-9">{{ op.created_at|date:"M d H:i:s" }}</dd>
{% if op.started_at %}<dt class="col-sm-3">{% trans "Started" %}</dt><dd class="col-sm-9">{{ op.started_at|date:"M d H:i:s" }}</dd>{% endif %}
{% if op.finished_at %}<dt class="col-sm-3">{% trans "Finished" %}</dt><dd class="col-sm-9">{{ op.finished_at|date:"M d H:i:s" }}</dd>{% endif %}
{% get_current_timezone as TIME_ZONE %}
<dt class="col-sm-3">{% blocktrans %}Created ({{ TIME_ZONE }}){% endblocktrans %}</dt><dd class="col-sm-9">{{ op.created_at|date:"Y-m-d H:i:s" }}</dd>
{% if op.started_at %}<dt class="col-sm-3">{% trans "Started" %}</dt><dd class="col-sm-9">{{ op.started_at|date:"Y-m-d H:i:s" }}</dd>{% endif %}
{% if op.finished_at %}<dt class="col-sm-3">{% trans "Finished" %}</dt><dd class="col-sm-9">{{ op.finished_at|date:"Y-m-d H:i:s" }}</dd>{% endif %}
{% if job.result.left_behind %}
<dt class="col-sm-3">{% trans "Left behind" %}</dt>
<dd class="col-sm-9">

View file

@ -3,6 +3,7 @@
{% load django_bootstrap5 %}
{% load bootstrap_icons %}
{% load operations %}
{% load tz %}
{% block title %}{% trans "Jobs" %}{% endblock %}
@ -14,7 +15,7 @@
{% if worker_unavailable %}
<div class="alert alert-warning shadow-sm">
{% bs_icon 'exclamation-triangle' %} {% trans "Worker unavailable, last seen" %}
{% if worker_last_seen %}{{ worker_last_seen|date:"M d H:i:s" }}{% else %}{% trans "never" %}{% endif %}
{% if worker_last_seen %}{{ worker_last_seen|date:"Y-m-d H:i:s" }}{% else %}{% trans "never" %}{% endif %}
</div>
{% endif %}
{% if request.user.is_superuser %}
@ -56,7 +57,8 @@
<table class="table table-hover table-striped align-middle">
<thead>
<tr>
<th scope="col" class="d-none d-md-table-cell">{% trans "Created" %}</th>
{% get_current_timezone as TIME_ZONE %}
<th scope="col" class="d-none d-md-table-cell">{% blocktrans %}Created ({{ TIME_ZONE }}){% endblocktrans %}</th>
<th scope="col">{% trans "Job" %}</th>
<th scope="col">{% trans "State" %}</th>
<th scope="col" class="d-none d-md-table-cell">{% trans "Resource" %}</th>
@ -66,7 +68,7 @@
<tbody>
{% for op, row in rows %}
<tr>
<td class="text-nowrap d-none d-md-table-cell">{{ op.created_at|date:"M d H:i:s" }}</td>
<td class="text-nowrap d-none d-md-table-cell">{{ op.created_at|date:"Y-m-d H:i:s" }}</td>
<td style="min-width: 10rem;">
<a href="{% url 'operations:job' row.id %}">{{ row.label }}</a>
{% if op.state in active_states and op.phase %}
@ -75,7 +77,7 @@
<div class="small text-body-secondary" title="{{ op.error }}">{{ op.error|truncatechars:80 }}</div>
{% endif %}
{# below md the hidden columns show here #}
<div class="d-md-none small text-body-secondary">{{ row.resource_label|wrap_at_slash }}<br>{{ op.created_at|date:"M d H:i:s" }}{% if request.user.is_superuser %} &middot; {{ row.username }}{% endif %}</div>
<div class="d-md-none small text-body-secondary">{{ row.resource_label|wrap_at_slash }}<br>{{ op.created_at|date:"Y-m-d H:i:s" }}{% if request.user.is_superuser %} &middot; {{ row.username }}{% endif %}</div>
</td>
<td>{% include "operations/state_badge.html" with state=op.state %}</td>
<td class="d-none d-md-table-cell">{{ row.resource_label|wrap_at_slash }}</td>

View file

@ -54,6 +54,13 @@ class JobsPageTestCase(TransactionTestCase):
self.assertContains(self.client.get(reverse("operations:jobs"), {"compute": "c"}), link)
self.assertNotContains(self.client.get(reverse("operations:jobs"), {"state": "failed"}), link)
def test_host_and_user_filters_match_part_of_the_name(self):
self.client.force_login(self.admin)
link = reverse("operations:job", args=[self.op.pk])
for filters in ({"compute": "C"}, {"username": "PAGE_U"}):
self.assertContains(self.client.get(reverse("operations:jobs"), filters), link)
self.assertNotContains(self.client.get(reverse("operations:jobs"), {"username": "other"}), link)
def test_resolve_needs_a_superuser_an_outcome_and_a_note(self):
jobs.finish(self.op, Outcome(Operation.UNCERTAIN, error="check"))
self.client.force_login(self.user)
@ -121,7 +128,8 @@ class JobsPageTestCase(TransactionTestCase):
self.client.force_login(self.admin)
for response in (self.client.get(reverse("operations:jobs")),
self.client.get(reverse("operations:job", args=[self.op.pk]))):
self.assertContains(response, "Oct 13 23:04:00")
self.assertContains(response, "2026-10-13 23:04:00")
self.assertContains(response, f"Created ({django_timezone.get_current_timezone_name()})")
self.assertNotContains(response, "2026-10-13T20:04")
def test_list_and_detail_refresh_while_a_job_is_active(self):
@ -196,12 +204,12 @@ class JobsPageTestCase(TransactionTestCase):
self.client.force_login(self.admin)
html = self.client.get(reverse("operations:jobs")).content.decode()
# below md only Job and State are columns
for header in ("Created", "Resource", "User"):
for header in (f"Created ({django_timezone.get_current_timezone_name()})", "Resource", "User"):
self.assertIn(f'<th scope="col" class="d-none d-md-table-cell">{header}</th>', html)
narrow = re.search(r'<div class="d-md-none small text-body-secondary">(.*?)</div>', html, re.S).group(1)
for text in ('<span class="text-nowrap">wvc-test-vm</span>', "page_user"):
self.assertIn(text, narrow)
self.assertRegex(narrow, r"[A-Z][a-z]{2} \d{2} \d{2}:\d{2}:\d{2}")
self.assertRegex(narrow, r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}")
# a plain user's narrow line has no user
self.client.force_login(self.user)
html = self.client.get(reverse("operations:jobs")).content.decode()

View file

@ -30,9 +30,12 @@ def _get(user, pk):
def jobs(request):
qs = _visible(request.user)
if request.user.is_superuser:
# the host and the user as typed: the start of the host, any part of the name
if compute := request.GET.get("compute"):
qs = qs.filter(resource_label__startswith=f"{compute}/")
for field in ("state", "kind", "username"):
qs = qs.filter(resource_label__istartswith=compute)
if username := request.GET.get("username"):
qs = qs.filter(username__icontains=username)
for field in ("state", "kind"):
if value := request.GET.get(field):
qs = qs.filter(**{field: value})
page = Paginator(qs, 50).get_page(request.GET.get("page"))