From b5e90efae6926f2cca4333d4a92360702186cb41 Mon Sep 17 00:00:00 2001 From: Paul Pepper <85895113+paulpepper-trade@users.noreply.github.com> Date: Wed, 27 Sep 2023 14:53:56 +0100 Subject: [PATCH] TP2000-1068 Envelope generation celery task info (#1051) * Add envelope task info to app-info page. * Better and consistent formatting. * App info page tests. --- common/jinja2/common/app_info.jinja | 113 ++++++++++++++++++++-------- common/tests/test_views.py | 30 +++++++- common/views.py | 71 ++++++++++++++--- 3 files changed, 167 insertions(+), 47 deletions(-) diff --git a/common/jinja2/common/app_info.jinja b/common/jinja2/common/app_info.jinja index 1080c062e..90b9acd6e 100644 --- a/common/jinja2/common/app_info.jinja +++ b/common/jinja2/common/app_info.jinja @@ -29,14 +29,57 @@ -
+ {% if request.user.is_superuser %} +
+
+

Deployment information

+ + {% set table_head = [ + {"text": "Name"}, + {"text": "Value"}, + {"text": "Description"}, + ] %} + + {% set table_rows = [ + [ + {"text": "GIT_BRANCH"}, + {"text": GIT_BRANCH}, + {"text": "Environment variable"}, + ], + [ + {"text": "GIT_COMMIT"}, + {"text": GIT_COMMIT}, + {"text": "Environment variable"}, + ], + [ + {"text": "APP_UPDATED_TIME"}, + {"text": APP_UPDATED_TIME}, + {"text": "Estimated application deploy time"}, + ], + [ + {"text": "LAST_TRANSACTION_TIME"}, + {"text": LAST_TRANSACTION_TIME}, + {"text": "Last transaction change time"}, + ], + ] %} + + {{ govukTable({ + "head": table_head, + "rows": table_rows + }) }} +
+
+ {% endif %} + + +

Active business rule checks

{% set table_head = [ {"text": "Workbasket ID"}, - {"text": "Time started"}, {"text": "Checks completed"}, + {"text": "Time started"}, {"text": "Celery task ID"}, ] %} {% set table_rows = [] %} @@ -46,15 +89,15 @@ {% for check in active_checks %} {{ table_rows.append([ {"text": check.workbasket_id}, - {"text": check.date_time_start}, {"text": check.checks_completed}, + {"text": check.date_time_start}, {"text": check.task_id}, ]) or "" }} {% endfor %} {% else %} {{ table_rows.append([ { - "text": "No active checks.", + "text": "No active business rule checks.", "colspan": 4, } ]) or "" }} @@ -76,39 +119,44 @@
- {% if request.user.is_superuser %} -
+
-

Deployment Information

+

Active envelope generation tasks

{% set table_head = [ - {"text": "Name"}, - {"text": "Value"}, - {"text": "Description"}, + {"text": "Packaged Workbasket ID"}, + {"text": "Workbasket ID"}, + {"text": "Time started"}, + {"text": "Celery task ID"}, ] %} + {% set table_rows = [] %} - {% set table_rows = [ - [ - {"text": "GIT_BRANCH"}, - {"text": GIT_BRANCH}, - {"text": "Environment variable"}, - ], - [ - {"text": "GIT_COMMIT"}, - {"text": GIT_COMMIT}, - {"text": "Environment variable"}, - ], - [ - {"text": "APP_UPDATED_TIME"}, - {"text": "{:%d %b %Y, %H:%M}".format(APP_UPDATED_TIME|localtime)}, - {"text": "Estimated application deploy time"}, - ], - [ - {"text": "LAST_TRANSACTION_TIME"}, - {"text": "{:%d %b %Y, %H:%M}".format(LAST_TRANSACTION_TIME|localtime)}, - {"text": "Last transaction change time"}, - ], - ] %} + {% if celery_healthy %} + {% if active_envelope_generation %} + {% for generation_info in active_envelope_generation %} + {{ table_rows.append([ + {"text": generation_info.packaged_workbasket_id}, + {"text": generation_info.workbasket_id}, + {"text": generation_info.date_time_start}, + {"text": generation_info.task_id}, + ]) or "" }} + {% endfor %} + {% else %} + {{ table_rows.append([ + { + "text": "No active envelope generation tasks.", + "colspan": 4, + } + ]) or "" }} + {% endif %} + {% else %} + {{ table_rows.append([ + { + "text": "Envelope generation task details are currently unavailable.", + "colspan": 4, + } + ]) or "" }} + {% endif %} {{ govukTable({ "head": table_head, @@ -116,6 +164,5 @@ }) }}
-{% endif %} {% endblock %} diff --git a/common/tests/test_views.py b/common/tests/test_views.py index 98cabe92f..2dfc9fa7c 100644 --- a/common/tests/test_views.py +++ b/common/tests/test_views.py @@ -118,13 +118,39 @@ def test_healthcheck_response(response, status_code, status): assert payload[1].tag == "response_time" -def test_app_info(valid_user_client): +def test_app_info_non_superuser(valid_user_client): + """Users without the superuser permission have a restricted view of + application information.""" response = valid_user_client.get(reverse("app-info")) assert response.status_code == 200 page = BeautifulSoup(str(response.content), "html.parser") - assert "Active business rule checks" in page.select("h2")[0].text + h2_elements = page.select(".info-section h2") + + assert len(h2_elements) == 2 + assert "Active business rule checks" in h2_elements[0].text + assert "Active envelope generation tasks" in h2_elements[1].text + + +def test_app_info_superuser(superuser_client, new_workbasket): + """ + Superusers should have an unrestricted view of application information. + + The new_workbasket fixture provides access to transaction information in the + deployment infomation section. + """ + response = superuser_client.get(reverse("app-info")) + + assert response.status_code == 200 + + page = BeautifulSoup(str(response.content), "html.parser") + h2_elements = page.select(".info-section h2") + + assert len(h2_elements) == 3 + assert "Deployment information" in h2_elements[0].text + assert "Active business rule checks" in h2_elements[1].text + assert "Active envelope generation tasks" in h2_elements[2].text def test_index_displays_footer_links(valid_user_client): diff --git a/common/views.py b/common/views.py index 74e829d01..8b95c115d 100644 --- a/common/views.py +++ b/common/views.py @@ -2,6 +2,7 @@ import os import time from datetime import datetime +from typing import Dict from typing import Optional from typing import Tuple from typing import Type @@ -41,6 +42,7 @@ from common.models import Transaction from common.pagination import build_pagination_list from common.validators import UpdateType +from publishing.models import PackagedWorkBasket from workbaskets.models import WorkBasket from workbaskets.views.mixins import WithCurrentWorkBasket @@ -129,16 +131,54 @@ class AppInfoView( TemplateView, ): template_name = "common/app_info.jinja" + DATETIME_FORMAT = "%d %b %Y, %H:%M" - def active_checks(self): - results = [] + def active_tasks(self) -> Dict: inspect = app.control.inspect() if not inspect: - return results + return {} active_tasks = inspect.active() if not active_tasks: - return results + return {} + + return active_tasks + + @staticmethod + def timestamp_to_datetime_string(timestamp): + return make_aware( + datetime.fromtimestamp(timestamp), + ).strftime(AppInfoView.DATETIME_FORMAT) + + def active_envelope_generation(self, active_tasks): + results = [] + + for _, task_info_list in active_tasks.items(): + for task_info in task_info_list: + if task_info.get("name") == "publishing.tasks.create_xml_envelope_file": + date_time_start = AppInfoView.timestamp_to_datetime_string( + task_info.get("time_start"), + ) + + packaged_workbasket_id = task_info.get("args", [""])[0] + packaged_workbasket = PackagedWorkBasket.objects.get( + id=packaged_workbasket_id, + ) + workbasket_id = packaged_workbasket.workbasket.id + + results.append( + { + "task_id": task_info.get("id"), + "packaged_workbasket_id": packaged_workbasket_id, + "workbasket_id": workbasket_id, + "date_time_start": date_time_start, + }, + ) + + return results + + def active_checks(self, active_tasks): + results = [] for _, task_info_list in active_tasks.items(): for task_info in task_info_list: @@ -146,11 +186,9 @@ def active_checks(self): task_info.get("name") == "workbaskets.tasks.call_check_workbasket_sync" ): - date_time_start = make_aware( - datetime.fromtimestamp( - task_info.get("time_start"), - ), - ).strftime("%Y-%m-%d, %H:%M:%S") + date_time_start = AppInfoView.timestamp_to_datetime_string( + task_info.get("time_start"), + ) workbasket_id = task_info.get("args", [""])[0] workbasket = WorkBasket.objects.get(id=workbasket_id) @@ -170,19 +208,28 @@ def active_checks(self): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) try: - data["active_checks"] = self.active_checks() + active_tasks = self.active_tasks() data["celery_healthy"] = True + data["active_checks"] = self.active_checks(active_tasks) + data["active_envelope_generation"] = self.active_envelope_generation( + active_tasks, + ) except kombu.exceptions.OperationalError as oe: data["celery_healthy"] = False if self.request.user.is_superuser: data["GIT_BRANCH"] = os.getenv("GIT_BRANCH", "Unavailable") data["GIT_COMMIT"] = os.getenv("GIT_COMMIT", "Unavailable") - data["APP_UPDATED_TIME"] = datetime.fromtimestamp( + data["APP_UPDATED_TIME"] = AppInfoView.timestamp_to_datetime_string( os.path.getmtime(__file__), ) + last_transaction = Transaction.objects.order_by("updated_at").last() data["LAST_TRANSACTION_TIME"] = ( - Transaction.objects.order_by("-updated_at").first().updated_at + format( + last_transaction.updated_at.strftime(AppInfoView.DATETIME_FORMAT), + ) + if last_transaction + else "No transactions" ) return data