feat: live transfer progress on remote-download task cards (#6)

The card progress bar froze at 100% once the download finished, giving
no signal during the import/transfer phase. Poll the existing
/workflow/progress endpoint while a task is in transfer phase and drive
both the bar and the status label from the live upload counters.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3582/head
Tomas Dvorak 2 weeks ago
parent f1b7b72a72
commit 6eae386b19

@ -17,7 +17,8 @@ import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useInView } from "react-intersection-observer"; import { useInView } from "react-intersection-observer";
import { FileType } from "../../../api/explorer.ts"; import { FileType } from "../../../api/explorer.ts";
import { TaskResponse, TaskType } from "../../../api/workflow.ts"; import { getTasksPhaseProgress } from "../../../api/api.ts";
import { TaskResponse, TaskStatus, TaskType } from "../../../api/workflow.ts";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import { DefaultButton } from "../../Common/StyledComponents.tsx"; import { DefaultButton } from "../../Common/StyledComponents.tsx";
import FileIcon from "../../FileManager/Explorer/FileIcon.tsx"; import FileIcon from "../../FileManager/Explorer/FileIcon.tsx";
@ -120,6 +121,42 @@ const TaskCard = ({ loading, showProgress, onLoad, onRetried, task }: TaskCardPr
}); });
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const [transferPercent, setTransferPercent] = useState<number | undefined>(undefined);
// During the transfer phase the summary's download counters are frozen at
// 100%; poll the live phase progress so the card bar keeps moving while
// files are imported to storage.
const transferring =
task?.type === TaskType.remote_download &&
task?.status === TaskStatus.processing &&
task?.summary?.phase === "transfer";
useEffect(() => {
if (!transferring || !task) {
setTransferPercent(undefined);
return;
}
let cancelled = false;
const fetchProgress = () => {
dispatch(getTasksPhaseProgress(task.id))
.then((res) => {
if (cancelled) {
return;
}
const upload = res?.["upload"];
if (upload && upload.total > 0) {
setTransferPercent((100 * upload.current) / upload.total);
}
})
.catch(() => {});
};
fetchProgress();
const iv = setInterval(fetchProgress, 2500);
return () => {
cancelled = true;
clearInterval(iv);
};
}, [transferring, task?.id]);
useEffect(() => { useEffect(() => {
if (!inView) { if (!inView) {
@ -152,7 +189,9 @@ const TaskCard = ({ loading, showProgress, onLoad, onRetried, task }: TaskCardPr
fullWidth fullWidth
percentage={ percentage={
showProgress showProgress
? ((task?.summary?.props?.download?.downloaded ?? 0) * 100) / ? transferring && transferPercent != undefined
? transferPercent
: ((task?.summary?.props?.download?.downloaded ?? 0) * 100) /
Math.max(task?.summary?.props?.download?.total ?? 0, 1) Math.max(task?.summary?.props?.download?.total ?? 0, 1)
: undefined : undefined
} }
@ -209,6 +248,7 @@ const TaskCard = ({ loading, showProgress, onLoad, onRetried, task }: TaskCardPr
status={task.status} status={task.status}
error={task.error} error={task.error}
summary={task.summary} summary={task.summary}
transferPercent={transferPercent}
/> />
)} )}
</Typography> </Typography>

@ -27,6 +27,8 @@ export interface TaskSummaryStatusProps {
summary?: TaskSummary; summary?: TaskSummary;
error?: string; error?: string;
simplified?: boolean; simplified?: boolean;
// Live transfer-phase progress percent for remote downloads.
transferPercent?: number;
} }
interface TaskStatusContentProps { interface TaskStatusContentProps {
@ -46,7 +48,7 @@ const TaskStatusContent = forwardRef(({ icon, title, color, ...props }: TaskStat
); );
}); });
const TaskSummaryStatus = ({ type, status, summary, error, simplified }: TaskSummaryStatusProps) => { const TaskSummaryStatus = ({ type, status, summary, error, simplified, transferPercent }: TaskSummaryStatusProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const theme = useTheme(); const theme = useTheme();
@ -111,7 +113,11 @@ const TaskSummaryStatus = ({ type, status, summary, error, simplified }: TaskSum
} else if (summary?.phase == "transfer") { } else if (summary?.phase == "transfer") {
return ( return (
<TaskStatusContent <TaskStatusContent
title={t("download.transferring")} title={
transferPercent != undefined
? `${t("download.transferring")} ${transferPercent.toFixed(0)}%`
: t("download.transferring")
}
icon={<ArrowSyncCircleFilledSpin fontSize={"small"} />} icon={<ArrowSyncCircleFilledSpin fontSize={"small"} />}
color={theme.palette.primary.main} color={theme.palette.primary.main}
/> />

Loading…
Cancel
Save