feat(stats): optimize dashboard data loading with internal ui endpoints

This commit is contained in:
Alexander Kalinovsky
2026-04-01 19:05:57 +03:00
parent 19d659df6b
commit 9b9c7b5575
11 changed files with 552 additions and 140 deletions

View File

@@ -55,10 +55,10 @@ def test_stats_returns_html_page_for_empty_board(tmp_path):
assert "Backlog" in response.text
assert "In Progress" in response.text
assert "Done" in response.text
assert response.text.count("No tasks") == 3
assert response.text.count("Loading tasks...") == 3
def test_stats_renders_tasks_in_all_kanban_columns(tmp_path):
def test_stats_includes_client_side_board_loading_hooks(tmp_path):
client, repo = make_client(tmp_path)
backlog = make_task(title="Backlog title", started=False, done=False)
in_progress = make_task(title="In progress title", started=True, done=False)
@@ -70,15 +70,16 @@ def test_stats_renders_tasks_in_all_kanban_columns(tmp_path):
response = client.get("/stats")
assert response.status_code == 200
assert "Backlog title" in response.text
assert "In progress title" in response.text
assert "Done title" in response.text
assert "Backlog" in response.text
assert "In Progress" in response.text
assert "Done" in response.text
assert 'id="backlog-list"' in response.text
assert 'id="in-progress-list"' in response.text
assert 'id="done-list"' in response.text
assert 'fetch("/ui_data/stats/board")' in response.text
def test_stats_preselects_first_task_in_details_block(tmp_path):
def test_stats_renders_empty_details_block_until_client_side_fetch(tmp_path):
client, repo = make_client(tmp_path)
first = make_task(title="First task", started=False, done=False)
second = make_task(title="Second task", started=True, done=False)
@@ -90,8 +91,8 @@ def test_stats_preselects_first_task_in_details_block(tmp_path):
assert response.status_code == 200
assert 'id="selected-title"' in response.text
assert 'id="selected-created-at"' in response.text
assert "First task" in response.text
assert first.created_at.isoformat() in response.text
assert "No task selected" in response.text
assert first.created_at.isoformat() not in response.text
def test_stats_shows_placeholders_for_missing_dates(tmp_path):
@@ -117,11 +118,11 @@ def test_stats_renders_cycle_time_for_completed_task(tmp_path):
assert response.status_code == 200
assert 'id="selected-cycle-time"' in response.text
assert "0h 9m" in response.text
assert ">—<" not in response.text.split('id="selected-cycle-time"', 1)[1].split("</div>", 2)[1]
assert "0h 9m" not in response.text
assert ">—<" in response.text
def test_stats_embeds_task_payload_for_client_side_switching(tmp_path):
def test_stats_does_not_embed_task_payload_for_client_side_switching(tmp_path):
client, repo = make_client(tmp_path)
task = make_task(title="Payload task", started=True, done=True)
repo.create_task(task)
@@ -129,14 +130,9 @@ def test_stats_embeds_task_payload_for_client_side_switching(tmp_path):
response = client.get("/stats")
assert response.status_code == 200
assert "data-task='" in response.text
assert str(task.id) in response.text
assert '"title": "Payload task"' in response.text
assert '"status": "done"' in response.text
assert '"created_at":' in response.text
assert '"started_at":' in response.text
assert '"done_at":' in response.text
assert '"cycle_time":' in response.text
assert "data-task='" not in response.text
assert str(task.id) not in response.text
assert "Payload task" not in response.text
def test_stats_includes_js_hooks_for_dynamic_selected_task_update(tmp_path):
@@ -154,7 +150,7 @@ def test_stats_includes_js_hooks_for_dynamic_selected_task_update(tmp_path):
assert 'id="selected-created-at"' in response.text
assert 'document.querySelectorAll(".task-card")' in response.text
assert 'card.addEventListener("click"' in response.text
assert "JSON.parse(card.dataset.task)" in response.text
assert "fetch(`/ui_data/tasks/${taskId}`)" in response.text
def test_stats_handles_utf8_titles(tmp_path):
@@ -164,7 +160,7 @@ def test_stats_handles_utf8_titles(tmp_path):
response = client.get("/stats")
assert response.status_code == 200
assert "Задача пример" in response.text
assert "Задача пример" not in response.text
def test_stats_escapes_html_in_task_title(tmp_path):
@@ -175,7 +171,7 @@ def test_stats_escapes_html_in_task_title(tmp_path):
assert response.status_code == 200
assert "<script>alert(1)</script>" not in response.text
assert "&lt;script&gt;alert(1)&lt;/script&gt;" in response.text
assert "&lt;script&gt;alert(1)&lt;/script&gt;" not in response.text
def test_stats_rejects_unsupported_method(tmp_path):
@@ -202,7 +198,7 @@ def test_stats_still_works_after_recovery_from_corrupted_file(tmp_path):
assert "No task selected" in response.text
def test_stats_renders_created_started_and_done_values_for_completed_task(tmp_path):
def test_stats_does_not_render_created_started_and_done_values_server_side(tmp_path):
client, repo = make_client(tmp_path)
task = make_task(title="Detailed task", started=True, done=True)
repo.create_task(task)
@@ -210,7 +206,6 @@ def test_stats_renders_created_started_and_done_values_for_completed_task(tmp_pa
response = client.get("/stats")
assert response.status_code == 200
assert task.created_at.isoformat() in response.text
assert task.started_at.isoformat() in response.text
assert task.done_at.isoformat() in response.text
assert task.created_at.isoformat() not in response.text
assert task.started_at.isoformat() not in response.text
assert task.done_at.isoformat() not in response.text