Show blocklist import error badge in navigation
When the most recent scheduled import completed with errors, surface the failure in the persistent app shell: - A warning MessageBar appears at top of main content area - An amber badge is rendered on the Blocklists sidebar nav item Backend: add last_run_errors: bool | None to ScheduleInfo model and populate it in get_schedule_info() from the latest import_log row. Frontend: extend ScheduleInfo type, add useBlocklistStatus polling hook, wire both indicators into MainLayout. Tests: 3 new service tests + 1 new router test (433 total, all pass).
This commit is contained in:
@@ -133,6 +133,8 @@ class ScheduleInfo(BaseModel):
|
||||
config: ScheduleConfig
|
||||
next_run_at: str | None
|
||||
last_run_at: str | None
|
||||
last_run_errors: bool | None = None
|
||||
"""``True`` if the most recent import had errors, ``False`` if clean, ``None`` if never run."""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -480,7 +480,13 @@ async def get_schedule_info(
|
||||
config = await get_schedule(db)
|
||||
last_log = await import_log_repo.get_last_log(db)
|
||||
last_run_at = last_log["timestamp"] if last_log else None
|
||||
return ScheduleInfo(config=config, next_run_at=next_run_at, last_run_at=last_run_at)
|
||||
last_run_errors: bool | None = (last_log["errors"] is not None) if last_log else None
|
||||
return ScheduleInfo(
|
||||
config=config,
|
||||
next_run_at=next_run_at,
|
||||
last_run_at=last_run_at,
|
||||
last_run_errors=last_run_errors,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -379,6 +379,31 @@ class TestGetSchedule:
|
||||
assert "next_run_at" in body
|
||||
assert "last_run_at" in body
|
||||
|
||||
async def test_schedule_response_includes_last_run_errors(
|
||||
self, bl_client: AsyncClient
|
||||
) -> None:
|
||||
"""GET /api/blocklists/schedule includes last_run_errors field."""
|
||||
info_with_errors = ScheduleInfo(
|
||||
config=ScheduleConfig(
|
||||
frequency=ScheduleFrequency.daily,
|
||||
interval_hours=24,
|
||||
hour=3,
|
||||
minute=0,
|
||||
day_of_week=0,
|
||||
),
|
||||
next_run_at=None,
|
||||
last_run_at="2026-03-01T03:00:00+00:00",
|
||||
last_run_errors=True,
|
||||
)
|
||||
with patch(
|
||||
"app.routers.blocklist.blocklist_service.get_schedule_info",
|
||||
new=AsyncMock(return_value=info_with_errors),
|
||||
):
|
||||
resp = await bl_client.get("/api/blocklists/schedule")
|
||||
body = resp.json()
|
||||
assert "last_run_errors" in body
|
||||
assert body["last_run_errors"] is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PUT /api/blocklists/schedule
|
||||
|
||||
@@ -254,7 +254,42 @@ class TestSchedule:
|
||||
assert loaded.interval_hours == 6
|
||||
|
||||
async def test_get_schedule_info_no_log(self, db: aiosqlite.Connection) -> None:
|
||||
"""get_schedule_info returns None for last_run_at when no log exists."""
|
||||
"""get_schedule_info returns None for last_run_at and last_run_errors when no log exists."""
|
||||
info = await blocklist_service.get_schedule_info(db, None)
|
||||
assert info.last_run_at is None
|
||||
assert info.next_run_at is None
|
||||
assert info.last_run_errors is None
|
||||
|
||||
async def test_get_schedule_info_no_errors_when_clean(
|
||||
self, db: aiosqlite.Connection
|
||||
) -> None:
|
||||
"""get_schedule_info returns last_run_errors=False when the last run had no errors."""
|
||||
from app.repositories import import_log_repo
|
||||
|
||||
await import_log_repo.add_log(
|
||||
db,
|
||||
source_id=None,
|
||||
source_url="https://example.test/ips.txt",
|
||||
ips_imported=10,
|
||||
ips_skipped=0,
|
||||
errors=None,
|
||||
)
|
||||
info = await blocklist_service.get_schedule_info(db, None)
|
||||
assert info.last_run_errors is False
|
||||
|
||||
async def test_get_schedule_info_errors_flag_when_failed(
|
||||
self, db: aiosqlite.Connection
|
||||
) -> None:
|
||||
"""get_schedule_info returns last_run_errors=True when the last run had errors."""
|
||||
from app.repositories import import_log_repo
|
||||
|
||||
await import_log_repo.add_log(
|
||||
db,
|
||||
source_id=None,
|
||||
source_url="https://example.test/ips.txt",
|
||||
ips_imported=0,
|
||||
ips_skipped=0,
|
||||
errors="Connection timeout",
|
||||
)
|
||||
info = await blocklist_service.get_schedule_info(db, None)
|
||||
assert info.last_run_errors is True
|
||||
|
||||
Reference in New Issue
Block a user