Skip to content

Commit

Permalink
Merge branch 'main' into controller-app
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrianDAlessandro authored Oct 4, 2024
2 parents 13c8507 + b2a6ed2 commit 4a26086
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 16 deletions.
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ repos:
- id: debug-statements
- id: trailing-whitespace
- id: end-of-file-fixer
exclude: .*/static/.*/js/.*
- id: pretty-format-json
args: [--autofix, --indent, '4', --no-sort]
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
Expand All @@ -32,6 +33,7 @@ repos:
hooks:
- id: codespell
args: [-L, .codespell_ignore.txt]
exclude: .*/static/.*/js/.*
- repo: https://github.com/djlint/djLint
rev: v1.35.2
hooks:
Expand Down
1 change: 1 addition & 0 deletions main/static/main/js/htmx.min.js

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion main/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@
class ProcessTable(tables.Table):
"""Defines and Process Table for the data from the Process Manager."""

class Meta: # noqa: D106
orderable = False

uuid = tables.Column(verbose_name="UUID")
name = tables.Column(verbose_name="Name")
user = tables.Column(verbose_name="User")
session = tables.Column(verbose_name="Session")
status_code = tables.Column(verbose_name="Status Code")
exit_code = tables.Column(verbose_name="Exit Code")
logs = tables.TemplateColumn(logs_column_template, verbose_name="Logs")
select = tables.CheckBoxColumn(accessor="uuid", verbose_name="Select")
select = tables.CheckBoxColumn(
accessor="uuid", verbose_name="Select", checked="checked"
)
1 change: 1 addition & 0 deletions main/templates/main/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
{% bootstrap_javascript %}
{% block extra_css %}
{% endblock extra_css %}
<script src="{% static 'main/js/htmx.min.js' %}"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
Expand Down
5 changes: 3 additions & 2 deletions main/templates/main/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{% extends "main/base.html" %}
{% load render_table from django_tables2 %}
{% block title %}
Home
{% endblock title %}
Expand Down Expand Up @@ -32,7 +31,9 @@
class="btn btn-danger"
name="action"
onclick="return confirm('Kill selected processes?')">
{% render_table table %}
<div hx-get="{% url 'main:process_table' %}"
hx-swap="outerHTML"
hx-trigger="load"></div>
</form>
</div>
<div class="col">
Expand Down
4 changes: 4 additions & 0 deletions main/templates/main/partials/process_table.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load render_table from django_tables2 %}
<div hx-post="{% url 'main:process_table' %}"
hx-trigger="load delay:1s, click from:input"
hx-swap="outerHTML">{% render_table table %}</div>
6 changes: 6 additions & 0 deletions main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
from . import views

app_name = "main"

partial_urlpatterns = [
path("process_table/", views.process_table, name="process_table"),
]

urlpatterns = [
path("", views.index, name="index"),
path("accounts/", include("django.contrib.auth.urls")),
path("process_action/", views.process_action, name="process_action"),
path("logs/<uuid:uuid>", views.logs, name="logs"),
path("boot_process/", views.BootProcessView.as_view(), name="boot_process"),
path("partials/", include(partial_urlpatterns)),
]
41 changes: 29 additions & 12 deletions main/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,39 +45,56 @@ async def get_session_info() -> ProcessInstanceList:
@login_required
def index(request: HttpRequest) -> HttpResponse:
"""View that renders the index/home page."""
val = asyncio.run(get_session_info())
with transaction.atomic():
# atomic to avoid race condition with kafka consumer
messages = request.session.load().get("messages", [])
request.session.pop("messages", [])
request.session.save()

context = {"messages": messages}
return render(request=request, context=context, template_name="main/index.html")


@login_required
def process_table(request: HttpRequest) -> HttpResponse:
"""Renders the process table.
This view may be called using either GET or POST methods. GET renders the table with
no check boxes selected. POST renders the table with checked boxes for any table row
with a uuid provided in the select key of the request data.
"""
selected_rows = request.POST.getlist("select", [])
session_info = asyncio.run(get_session_info())

status_enum_lookup = dict(item[::-1] for item in ProcessInstance.StatusCode.items())

table_data = []
process_instances = val.data.values
process_instances = session_info.data.values
for process_instance in process_instances:
metadata = process_instance.process_description.metadata
uuid = process_instance.uuid.uuid
table_data.append(
{
"uuid": process_instance.uuid.uuid,
"uuid": uuid,
"name": metadata.name,
"user": metadata.user,
"session": metadata.session,
"status_code": status_enum_lookup[process_instance.status_code],
"exit_code": process_instance.return_code,
"checked": (uuid in selected_rows),
}
)

table = ProcessTable(table_data)

# sort table data based on request parameters
table_configurator = django_tables2.RequestConfig(request)
table_configurator.configure(table)

with transaction.atomic():
# atomic to avoid race condition with kafka consumer
messages = request.session.load().get("messages", [])
request.session.pop("messages", [])
request.session.save()

context = {"table": table, "messages": messages}
return render(request=request, context=context, template_name="main/index.html")
return render(
request=request,
context=dict(table=table),
template_name="main/partials/process_table.html",
)


# an enum for process actions
Expand Down
42 changes: 41 additions & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from http import HTTPStatus
from unittest.mock import MagicMock
from uuid import uuid4

import pytest
from django.urls import reverse
from pytest_django.asserts import assertContains, assertTemplateUsed

from main.tables import ProcessTable
from main.views import ProcessAction

from .utils import LoginRequiredTest
Expand All @@ -28,7 +30,6 @@ def test_index_view_admin(self, admin_client, mocker):
with assertTemplateUsed(template_name="main/index.html"):
response = admin_client.get(self.endpoint)
assert response.status_code == HTTPStatus.OK
assert "table" in response.context
assertContains(response, "Boot</a>")

def test_session_messages(self, auth_client, mocker):
Expand Down Expand Up @@ -167,3 +168,42 @@ async def test_boot_process(mocker, dummy_session_data):
mock.return_value.dummy_boot.assert_called_once_with(
user="root", **dummy_session_data
)


class TestProcessTableView(LoginRequiredTest):
"""Test the main.views.process_table view function."""

endpoint = reverse("main:process_table")

@pytest.mark.parametrize("method", ("get", "post"))
def test_method(self, method, auth_client, mocker):
"""Tests basic calls of view method."""
self._mock_session_info(mocker, [])
response = getattr(auth_client, method)(self.endpoint)
assert response.status_code == HTTPStatus.OK
assert isinstance(response.context["table"], ProcessTable)

def _mock_session_info(self, mocker, uuids):
"""Mocks views.get_session_info with ProcessInstanceList like data."""
mock = mocker.patch("main.views.get_session_info")
instance_mocks = [MagicMock() for uuid in uuids]
for instance_mock, uuid in zip(instance_mocks, uuids):
instance_mock.uuid.uuid = str(uuid)
instance_mock.status_code = 0
mock.data.values.__iter__.return_value = instance_mocks
return mock

def test_post_checked_rows(self, mocker, auth_client):
"""Tests table data is correct when post data is included."""
all_uuids = [str(uuid4()) for _ in range(5)]
selected_uuids = all_uuids[::2]

self._mock_session_info(mocker, all_uuids)

response = auth_client.post(self.endpoint, data=dict(select=selected_uuids))
assert response.status_code == HTTPStatus.OK
table = response.context["table"]
assert isinstance(table, ProcessTable)

for row in table.data.data:
assert row["checked"] == (row["uuid"] in selected_uuids)

0 comments on commit 4a26086

Please sign in to comment.