From aabce6b03747ff13f66aae23b118b18074969521 Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 15 Oct 2026 08:18:40 +0300 Subject: [PATCH] fix(operations): the Jobs pages refresh while a job is active The list and the detail page were rendered once, so a job that finished kept showing running until the page was reloaded by hand. While a shown job is queued, running or cancelling, the page now reloads every five seconds (not while hidden or while a form field has focus). --- operations/templates/operations/follow_active.html | 8 ++++++++ operations/templates/operations/job.html | 3 +++ operations/templates/operations/jobs.html | 3 +++ operations/tests/test_views.py | 9 +++++++++ operations/views.py | 4 +++- 5 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 operations/templates/operations/follow_active.html diff --git a/operations/templates/operations/follow_active.html b/operations/templates/operations/follow_active.html new file mode 100644 index 00000000..574df4de --- /dev/null +++ b/operations/templates/operations/follow_active.html @@ -0,0 +1,8 @@ +{# Reloads the page while it shows an active job, so the page follows the job to its end. #} + diff --git a/operations/templates/operations/job.html b/operations/templates/operations/job.html index 564e923d..ef53f97a 100644 --- a/operations/templates/operations/job.html +++ b/operations/templates/operations/job.html @@ -97,3 +97,6 @@ {% endblock %} +{% block script %} + {% if follow %}{% include "operations/follow_active.html" %}{% endif %} +{% endblock script %} diff --git a/operations/templates/operations/jobs.html b/operations/templates/operations/jobs.html index c8b158bf..e072cdcb 100644 --- a/operations/templates/operations/jobs.html +++ b/operations/templates/operations/jobs.html @@ -92,3 +92,6 @@ {% endblock %} +{% block script %} + {% if follow %}{% include "operations/follow_active.html" %}{% endif %} +{% endblock script %} diff --git a/operations/tests/test_views.py b/operations/tests/test_views.py index 482aa3f5..d3e27b40 100644 --- a/operations/tests/test_views.py +++ b/operations/tests/test_views.py @@ -117,6 +117,15 @@ class JobsPageTestCase(TransactionTestCase): self.assertContains(response, "Oct 13 23:04:00") self.assertNotContains(response, "2026-10-13T20:04") + def test_list_and_detail_refresh_while_a_job_is_active(self): + self.client.force_login(self.user) + pages = (reverse("operations:jobs"), reverse("operations:job", args=[self.op.pk])) + for url in pages: # the job is queued + self.assertContains(self.client.get(url), 'id="follow-active-jobs"') + Operation.objects.filter(pk=self.op.pk).update(state=Operation.SUCCEEDED) + for url in pages: + self.assertNotContains(self.client.get(url), 'id="follow-active-jobs"') + def test_list_and_detail_show_a_state_badge(self): Operation.objects.filter(pk=self.op.pk).update(state=Operation.FAILED, error="disk busy") self.client.force_login(self.admin) diff --git a/operations/views.py b/operations/views.py index 6f07bca8..96f659a5 100644 --- a/operations/views.py +++ b/operations/views.py @@ -45,6 +45,7 @@ def jobs(request): return render(request, "operations/jobs.html", {"page": page, "rows": rows, "worker_last_seen": seen, "worker_unavailable": unavailable, "filters": request.GET, "active_states": Operation.ACTIVE, + "follow": any(op.state in Operation.ACTIVE for op in page.object_list), "states": Operation.ACTIVE + Operation.TERMINAL, "kinds": sorted((name, _(kind.label)) for name, kind in jobs_module.KINDS.items()), "page_query": urlencode({k: v for k, v in request.GET.items() if k != "page"})}) @@ -55,7 +56,8 @@ def job(request, pk): kind = jobs_module.KINDS.get(op.kind) return render(request, "operations/job.html", {"job": OperationSerializer(op, context={"request": request}).data, "op": op, - "progress_bytes": kind is not None and kind.progress_bytes}) + "progress_bytes": kind is not None and kind.progress_bytes, + "follow": op.state in Operation.ACTIVE}) @require_POST