Implement tab-scoped runs pagination state

This commit is contained in:
Abel Luck 2026-03-31 12:12:36 +02:00
parent dce67ea9e3
commit c834c3c254
6 changed files with 444 additions and 62 deletions

View file

@ -1,12 +1,15 @@
from __future__ import annotations
import asyncio
import json
import os
import re
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any, cast
import pytest
from repub.components import action_button, status_badge, toggle_field
from repub.datastar import RefreshBroker, render_sse_event, render_stream
from repub.jobs import load_dashboard_view
@ -26,6 +29,7 @@ from repub.pages.sources import sources_page
from repub.web import (
create_app,
get_refresh_broker,
get_tab_state_store,
render_create_source,
render_dashboard,
render_edit_source,
@ -228,8 +232,8 @@ def test_runs_page_renders_clear_completed_button_and_pagination() -> None:
assert ">Clear history<" in body
assert "Showing" in body
assert "21" in body
assert 'href="/runs?completed_page=1"' in body
assert 'href="/runs?completed_page=2"' in body
assert "@post(&#39;/actions/runs/completed-page/1&#39;)" in body
assert "@post(&#39;/actions/runs/completed-page/2&#39;)" in body
assert 'aria-current="page"' in body
@ -1314,6 +1318,142 @@ def test_render_runs_shows_empty_state_rows(monkeypatch, tmp_path: Path) -> None
asyncio.run(run())
def test_runs_pagination_action_updates_only_the_current_tab(
monkeypatch, tmp_path: Path
) -> None:
db_path = tmp_path / "runs-tab-pagination.db"
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
async def run() -> None:
app = create_app()
client = app.test_client()
source = create_source(
name="Paged runs source",
slug="paged-runs-source",
source_type="feed",
notes="",
spider_arguments="",
enabled=True,
cron_minute="*/30",
cron_hour="*",
cron_day_of_month="*",
cron_day_of_week="*",
cron_month="*",
feed_url="https://example.com/paged-runs.xml",
)
job = Job.get(Job.source == source)
for minute in range(21):
JobExecution.create(
job=job,
ended_at=datetime(2026, 3, 30, 12, minute, tzinfo=UTC),
running_status=JobExecutionStatus.SUCCEEDED,
)
async with client.request(
"/runs?u=shim",
method="POST",
headers={
"Datastar-Request": "true",
"Content-Type": "application/json",
},
) as first_connection:
async with client.request(
"/runs?u=shim",
method="POST",
headers={
"Datastar-Request": "true",
"Content-Type": "application/json",
},
) as second_connection:
await first_connection.send(json.dumps({"tabid": "tab-1"}).encode())
await second_connection.send(json.dumps({"tabid": "tab-2"}).encode())
await first_connection.send_complete()
await second_connection.send_complete()
first_body = (
await asyncio.wait_for(first_connection.receive(), timeout=1)
).decode()
second_body = (
await asyncio.wait_for(second_connection.receive(), timeout=1)
).decode()
assert (
'href="/runs?completed_page=1" aria-current="page"'
not in first_body
)
assert (
'Showing <span class="font-medium text-slate-950">1</span> to '
'<span class="font-medium text-slate-950">20</span> of '
'<span class="font-medium text-slate-950">21</span> results'
) in first_body
assert (
'Showing <span class="font-medium text-slate-950">1</span> to '
'<span class="font-medium text-slate-950">20</span> of '
'<span class="font-medium text-slate-950">21</span> results'
) in second_body
response = await client.post(
"/actions/runs/completed-page/2",
headers={"Datastar-Request": "true"},
json={"tabid": "tab-1"},
)
assert response.status_code == 204
updated_first_body = (
await asyncio.wait_for(first_connection.receive(), timeout=1)
).decode()
assert (
'Showing <span class="font-medium text-slate-950">21</span> to '
'<span class="font-medium text-slate-950">21</span> of '
'<span class="font-medium text-slate-950">21</span> results'
) in updated_first_body
assert 'aria-current="page"' in updated_first_body
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(second_connection.receive(), timeout=0.2)
await second_connection.disconnect()
await first_connection.disconnect()
asyncio.run(run())
def test_runs_patch_creates_and_cleans_up_tab_state(
monkeypatch, tmp_path: Path
) -> None:
db_path = tmp_path / "runs-tab-state.db"
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
async def run() -> None:
app = create_app()
client = app.test_client()
async with client.request(
"/runs?u=shim",
method="POST",
headers={
"Datastar-Request": "true",
"Content-Type": "application/json",
},
) as connection:
await connection.send(json.dumps({"tabid": "tab-1"}).encode())
await connection.send_complete()
await asyncio.wait_for(connection.receive(), timeout=1)
assert get_tab_state_store(app).get_tab_state("tab-1") == {}
await connection.disconnect()
await asyncio.sleep(0)
assert get_tab_state_store(app).get_tab_state("tab-1") is None
asyncio.run(run())
def test_render_runs_keeps_queued_execution_in_scheduled_jobs_table(
monkeypatch, tmp_path: Path
) -> None: