add empty table placeholders

This commit is contained in:
Abel Luck 2026-03-30 15:28:56 +02:00
parent 8716579508
commit 0803617e62
5 changed files with 60 additions and 6 deletions

View file

@ -190,6 +190,7 @@ def table_section(
eyebrow: str | None = None, eyebrow: str | None = None,
title: str, title: str,
subtitle: str | None = None, subtitle: str | None = None,
empty_message: str,
headers: tuple[str, ...], headers: tuple[str, ...],
rows: tuple[tuple[Node, ...], ...], rows: tuple[tuple[Node, ...], ...],
actions: Node | None = None, actions: Node | None = None,
@ -208,6 +209,17 @@ def table_section(
), ),
] ]
body_rows: Node
if rows:
body_rows = (render_row(row) for row in rows)
else:
body_rows = h.tr[
h.td(
colspan=str(len(headers)),
class_="px-4 py-8 text-center text-sm text-slate-500 sm:px-6",
)[empty_message]
]
return h.section[ return h.section[
h.div(class_="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between")[ h.div(class_="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between")[
h.div[ h.div[
@ -238,9 +250,7 @@ def table_section(
) )
] ]
], ],
h.tbody(class_="divide-y divide-slate-200 bg-white")[ h.tbody(class_="divide-y divide-slate-200 bg-white")[body_rows],
(render_row(row) for row in rows)
],
] ]
] ]
], ],

View file

@ -143,6 +143,17 @@ def running_executions_table(
), ),
] ]
body_rows: Node
if rows:
body_rows = (render_row(row) for row in rows)
else:
body_rows = h.tr[
h.td(
colspan=str(len(headers)),
class_="px-4 py-8 text-center text-sm text-slate-500",
)["No job executions are running."]
]
return h.section[ return h.section[
h.div(class_="mb-3 flex items-end justify-between gap-4")[ h.div(class_="mb-3 flex items-end justify-between gap-4")[
h.div[ h.div[
@ -173,9 +184,7 @@ def running_executions_table(
) )
] ]
], ],
h.tbody(class_="divide-y divide-slate-200 bg-white")[ h.tbody(class_="divide-y divide-slate-200 bg-white")[body_rows],
(render_row(row) for row in rows)
],
] ]
] ]
], ],
@ -225,6 +234,7 @@ def published_feeds_table(
return table_section( return table_section(
eyebrow="Published feeds", eyebrow="Published feeds",
title="Published feeds", title="Published feeds",
empty_message="No feeds have been published yet.",
headers=("Source", "Feed URL", "Status", "Last updated", "Disk usage"), headers=("Source", "Feed URL", "Status", "Last updated", "Disk usage"),
rows=rows, rows=rows,
actions=muted_action_link(href="/sources", label="Manage sources"), actions=muted_action_link(href="/sources", label="Manage sources"),

View file

@ -211,6 +211,7 @@ def runs_page(
table_section( table_section(
eyebrow="Live work", eyebrow="Live work",
title="Running job executions", title="Running job executions",
empty_message="No job executions are running.",
headers=( headers=(
"Source", "Source",
"Execution", "Execution",
@ -224,6 +225,7 @@ def runs_page(
table_section( table_section(
eyebrow="Queue", eyebrow="Queue",
title="Upcoming jobs", title="Upcoming jobs",
empty_message="No jobs are scheduled.",
headers=( headers=(
"Source", "Source",
"Next run", "Next run",
@ -237,6 +239,7 @@ def runs_page(
table_section( table_section(
eyebrow="History", eyebrow="History",
title="Completed job executions", title="Completed job executions",
empty_message="No job executions have completed yet.",
headers=( headers=(
"Source", "Source",
"Execution", "Execution",

View file

@ -92,6 +92,7 @@ def sources_table(
return table_section( return table_section(
eyebrow="Inventory", eyebrow="Inventory",
title="Sources", title="Sources",
empty_message="No sources yet.",
headers=("Source", "Type", "Upstream", "Schedule", "Job state", "Actions"), headers=("Source", "Type", "Upstream", "Schedule", "Job state", "Actions"),
rows=rows, rows=rows,
actions=header_action_link(href="/sources/create", label="Create source"), actions=header_action_link(href="/sources/create", label="Create source"),

View file

@ -215,6 +215,20 @@ def test_render_dashboard_shows_dashboard_information_architecture(
asyncio.run(run()) asyncio.run(run())
def test_render_dashboard_shows_empty_state_rows(monkeypatch, tmp_path: Path) -> None:
db_path = tmp_path / "dashboard-empty.db"
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
async def run() -> None:
app = create_app()
body = str(await render_dashboard(app))
assert "No job executions are running." in body
assert "No feeds have been published yet." in body
asyncio.run(run())
def test_load_dashboard_view_measures_log_artifact_path( def test_load_dashboard_view_measures_log_artifact_path(
monkeypatch, tmp_path: Path monkeypatch, tmp_path: Path
) -> None: ) -> None:
@ -390,6 +404,7 @@ def test_render_sources_shows_table_and_create_link() -> None:
assert ">Sources<" in body assert ">Sources<" in body
assert 'href="/sources/create"' in body assert 'href="/sources/create"' in body
assert "No sources yet." in body
assert "guardian-feed" not in body assert "guardian-feed" not in body
assert "podcast-audio" not in body assert "podcast-audio" not in body
@ -840,6 +855,21 @@ def test_render_runs_shows_running_upcoming_and_completed_tables(
asyncio.run(run()) asyncio.run(run())
def test_render_runs_shows_empty_state_rows(monkeypatch, tmp_path: Path) -> None:
db_path = tmp_path / "runs-empty.db"
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))
async def run() -> None:
app = create_app()
body = str(await render_runs(app))
assert body.count("No job executions are running.") == 1
assert "No jobs are scheduled." in body
assert "No job executions have completed yet." in body
asyncio.run(run())
def test_render_execution_logs_uses_app_route(monkeypatch, tmp_path: Path) -> None: def test_render_execution_logs_uses_app_route(monkeypatch, tmp_path: Path) -> None:
db_path = tmp_path / "logs-render.db" db_path = tmp_path / "logs-render.db"
monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path)) monkeypatch.setenv("REPUBLISHER_DB_PATH", str(db_path))