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 = []
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,
},
+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.",
)
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):
+18 -6
View File
@@ -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<ActionHistory[]>([])
const [operationProgress, setOperationProgress] = useState<OperationProgress | null>(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() {
<>
<div className="request-stage-topline">
<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>
<h3>{stage.label}</h3>
<p>{stage.summary}</p>