Clarify and live-refresh media indexing state
Magent CI/CD / verify (push) Successful in 10m43s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 26s

This commit is contained in:
2026-09-01 09:41:15 +12:00
parent ae6cee5d0b
commit b0eff9ffcf
3 changed files with 48 additions and 11 deletions
+18 -5
View File
@@ -661,13 +661,25 @@ def _build_presentation(
pipeline_torrents = [] pipeline_torrents = []
if jellyfin_partial: 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: 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": 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: 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) display_download = dict(download)
if fully_available: if fully_available:
@@ -723,8 +735,9 @@ def _build_presentation(
}, },
{ {
"id": "available", "id": "available",
"label": "Available", "label": available_label,
"state": available_state, "state": available_state,
"stateLabel": available_state_label,
"summary": available_summary, "summary": available_summary,
"link": jellyfin_link, "link": jellyfin_link,
}, },
+12
View File
@@ -475,8 +475,14 @@ class RequestPresentationTests(unittest.TestCase):
"The requested content has been collected and is available to watch. No further action is needed.", "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") 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["state"], "complete")
self.assertEqual(search_stage["actionIds"], []) 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: def test_partially_available_content_keeps_missing_download_attention(self) -> None:
snapshot = Snapshot( snapshot = Snapshot(
@@ -546,6 +552,12 @@ class RequestPresentationTests(unittest.TestCase):
self.assertEqual(search_stage["state"], "complete") self.assertEqual(search_stage["state"], "complete")
self.assertEqual(download_stage["state"], "complete") self.assertEqual(download_stage["state"], "complete")
self.assertEqual(available_stage["state"], "active") 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): class RequestCreationFlowTests(unittest.IsolatedAsyncioTestCase):
+18 -6
View File
@@ -23,6 +23,7 @@ type PipelineStage = {
id: string id: string
label: string label: string
state: 'complete' | 'active' | 'partial' | 'attention' | 'waiting' | string state: 'complete' | 'active' | 'partial' | 'attention' | 'waiting' | string
stateLabel?: string
summary: string summary: string
available?: number available?: number
missing?: number missing?: number
@@ -227,6 +228,7 @@ const fallbackStatusLabel = (state: string) => {
const fallbackPipeline = (snapshot: Snapshot): PipelineStage[] => { const fallbackPipeline = (snapshot: Snapshot): PipelineStage[] => {
const approved = snapshot.state !== 'REQUESTED' const approved = snapshot.state !== 'REQUESTED'
const complete = ['COMPLETED', 'AVAILABLE'].includes(snapshot.state) const complete = ['COMPLETED', 'AVAILABLE'].includes(snapshot.state)
const indexing = snapshot.state === 'IMPORTING'
return [ return [
{ id: 'requested', label: 'Requested', state: 'complete', summary: 'Request received' }, { 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: 'download', label: 'Download', state: 'waiting', summary: 'No download attempt yet' },
{ {
id: 'available', id: 'available',
label: 'Available', label: complete ? 'Available to watch' : indexing ? 'Adding to Grizzlyflix' : 'Media server',
state: complete ? 'complete' : 'waiting', state: complete ? 'complete' : indexing ? 'active' : 'waiting',
summary: complete ? 'Available to watch' : 'Not available on the media server yet', 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, link: snapshot.raw?.jellyfin?.link,
}, },
] ]
@@ -276,6 +283,11 @@ export default function RequestTimelinePage() {
const [historyActions, setHistoryActions] = useState<ActionHistory[]>([]) const [historyActions, setHistoryActions] = useState<ActionHistory[]>([])
const [operationProgress, setOperationProgress] = useState<OperationProgress | null>(null) const [operationProgress, setOperationProgress] = useState<OperationProgress | null>(null)
const [isAdmin, setIsAdmin] = useState(false) const [isAdmin, setIsAdmin] = useState(false)
const awaitingMediaIndex = Boolean(
snapshot?.presentation?.pipeline?.some(
(stage) => stage.id === 'available' && stage.state === 'active'
)
)
useEffect(() => { useEffect(() => {
if (!requestId) return if (!requestId) return
@@ -355,12 +367,12 @@ export default function RequestTimelinePage() {
if (!stopped) console.error(error) 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 () => { return () => {
stopped = true stopped = true
window.clearInterval(timer) window.clearInterval(timer)
} }
}, [requestId, router]) }, [awaitingMediaIndex, requestId, router])
const liveDownloadKey = useMemo(() => { const liveDownloadKey = useMemo(() => {
const downloadStage = snapshot?.presentation?.pipeline?.find((stage) => stage.id === 'download') const downloadStage = snapshot?.presentation?.pipeline?.find((stage) => stage.id === 'download')
@@ -741,7 +753,7 @@ export default function RequestTimelinePage() {
<> <>
<div className="request-stage-topline"> <div className="request-stage-topline">
<span className="request-stage-number">{String(index + 1).padStart(2, '0')}</span> <span className="request-stage-number">{String(index + 1).padStart(2, '0')}</span>
<span className={`request-stage-state state-${stage.state}`}>{stage.state}</span> <span className={`request-stage-state state-${stage.state}`}>{stage.stateLabel ?? stage.state}</span>
</div> </div>
<h3>{stage.label}</h3> <h3>{stage.label}</h3>
<p>{stage.summary}</p> <p>{stage.summary}</p>