From b0eff9ffcf2ec3b967a6f8c1033f6738eebac760 Mon Sep 17 00:00:00 2001 From: Zak Bearman Date: Tue, 1 Sep 2026 09:41:15 +1200 Subject: [PATCH] Clarify and live-refresh media indexing state --- backend/app/services/snapshot.py | 23 ++++++++++++++++++----- backend/tests/test_backend_quality.py | 12 ++++++++++++ frontend/app/requests/[id]/page.tsx | 24 ++++++++++++++++++------ 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/backend/app/services/snapshot.py b/backend/app/services/snapshot.py index ba15a9d..26757aa 100644 --- a/backend/app/services/snapshot.py +++ b/backend/app/services/snapshot.py @@ -661,13 +661,25 @@ def _build_presentation( pipeline_torrents = [] if jellyfin_partial: - available_state, available_summary = "partial", f"{available} of {total} episodes available" + available_label = "Partially available" + available_state = "partial" + available_state_label = "Partly ready" + available_summary = f"{available} of {total} episodes are ready to watch in Grizzlyflix." elif jellyfin_found: - available_state, available_summary = "complete", "Available to watch" + available_label = "Available to watch" + available_state = "complete" + available_state_label = "Ready" + available_summary = "This title is ready to watch in Grizzlyflix." elif arr_state == "available": - available_state, available_summary = "active", "Waiting for the media server to index this title" + available_label = "Adding to Grizzlyflix" + available_state = "active" + available_state_label = "Indexing" + available_summary = "The download is complete. Grizzlyflix is indexing this title now." else: - available_state, available_summary = "waiting", "Not available on the media server yet" + available_label = "Media server" + available_state = "waiting" + available_state_label = "Waiting" + available_summary = "This title has not reached Grizzlyflix yet." display_download = dict(download) if fully_available: @@ -723,8 +735,9 @@ def _build_presentation( }, { "id": "available", - "label": "Available", + "label": available_label, "state": available_state, + "stateLabel": available_state_label, "summary": available_summary, "link": jellyfin_link, }, diff --git a/backend/tests/test_backend_quality.py b/backend/tests/test_backend_quality.py index 9b67453..2e9451d 100644 --- a/backend/tests/test_backend_quality.py +++ b/backend/tests/test_backend_quality.py @@ -475,8 +475,14 @@ class RequestPresentationTests(unittest.TestCase): "The requested content has been collected and is available to watch. No further action is needed.", ) search_stage = next(stage for stage in presentation["pipeline"] if stage["id"] == "search") + available_stage = next(stage for stage in presentation["pipeline"] if stage["id"] == "available") self.assertEqual(search_stage["state"], "complete") self.assertEqual(search_stage["actionIds"], []) + self.assertEqual(available_stage["state"], "complete") + self.assertEqual(available_stage["stateLabel"], "Ready") + self.assertEqual(available_stage["label"], "Available to watch") + self.assertEqual(available_stage["summary"], "This title is ready to watch in Grizzlyflix.") + self.assertEqual(available_stage["link"], "https://media.test/title/3909") def test_partially_available_content_keeps_missing_download_attention(self) -> None: snapshot = Snapshot( @@ -546,6 +552,12 @@ class RequestPresentationTests(unittest.TestCase): self.assertEqual(search_stage["state"], "complete") self.assertEqual(download_stage["state"], "complete") self.assertEqual(available_stage["state"], "active") + self.assertEqual(available_stage["stateLabel"], "Indexing") + self.assertEqual(available_stage["label"], "Adding to Grizzlyflix") + self.assertEqual( + available_stage["summary"], + "The download is complete. Grizzlyflix is indexing this title now.", + ) class RequestCreationFlowTests(unittest.IsolatedAsyncioTestCase): diff --git a/frontend/app/requests/[id]/page.tsx b/frontend/app/requests/[id]/page.tsx index ad1cd90..b463085 100644 --- a/frontend/app/requests/[id]/page.tsx +++ b/frontend/app/requests/[id]/page.tsx @@ -23,6 +23,7 @@ type PipelineStage = { id: string label: string state: 'complete' | 'active' | 'partial' | 'attention' | 'waiting' | string + stateLabel?: string summary: string available?: number missing?: number @@ -227,6 +228,7 @@ const fallbackStatusLabel = (state: string) => { const fallbackPipeline = (snapshot: Snapshot): PipelineStage[] => { const approved = snapshot.state !== 'REQUESTED' const complete = ['COMPLETED', 'AVAILABLE'].includes(snapshot.state) + const indexing = snapshot.state === 'IMPORTING' return [ { id: 'requested', label: 'Requested', state: 'complete', summary: 'Request received' }, { @@ -245,9 +247,14 @@ const fallbackPipeline = (snapshot: Snapshot): PipelineStage[] => { { id: 'download', label: 'Download', state: 'waiting', summary: 'No download attempt yet' }, { id: 'available', - label: 'Available', - state: complete ? 'complete' : 'waiting', - summary: complete ? 'Available to watch' : 'Not available on the media server yet', + label: complete ? 'Available to watch' : indexing ? 'Adding to Grizzlyflix' : 'Media server', + state: complete ? 'complete' : indexing ? 'active' : 'waiting', + stateLabel: complete ? 'Ready' : indexing ? 'Indexing' : 'Waiting', + summary: complete + ? 'This title is ready to watch in Grizzlyflix.' + : indexing + ? 'The download is complete. Grizzlyflix is indexing this title now.' + : 'This title has not reached Grizzlyflix yet.', link: snapshot.raw?.jellyfin?.link, }, ] @@ -276,6 +283,11 @@ export default function RequestTimelinePage() { const [historyActions, setHistoryActions] = useState([]) const [operationProgress, setOperationProgress] = useState(null) const [isAdmin, setIsAdmin] = useState(false) + const awaitingMediaIndex = Boolean( + snapshot?.presentation?.pipeline?.some( + (stage) => stage.id === 'available' && stage.state === 'active' + ) + ) useEffect(() => { if (!requestId) return @@ -355,12 +367,12 @@ export default function RequestTimelinePage() { if (!stopped) console.error(error) } } - const timer = window.setInterval(() => void refresh(), 15_000) + const timer = window.setInterval(() => void refresh(), awaitingMediaIndex ? 5_000 : 15_000) return () => { stopped = true window.clearInterval(timer) } - }, [requestId, router]) + }, [awaitingMediaIndex, requestId, router]) const liveDownloadKey = useMemo(() => { const downloadStage = snapshot?.presentation?.pipeline?.find((stage) => stage.id === 'download') @@ -741,7 +753,7 @@ export default function RequestTimelinePage() { <>
{String(index + 1).padStart(2, '0')} - {stage.state} + {stage.stateLabel ?? stage.state}

{stage.label}

{stage.summary}