feat: B4 - progressbar
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -263,6 +263,51 @@ class TasksApiGoldenTest(unittest.TestCase):
|
|||||||
self.assertEqual(body["status"], "ready")
|
self.assertEqual(body["status"], "ready")
|
||||||
self.assertEqual(body["destination"], "docs.zip")
|
self.assertEqual(body["destination"], "docs.zip")
|
||||||
|
|
||||||
|
def test_get_task_detail_requested_archive_download(self) -> None:
|
||||||
|
self._insert_task(
|
||||||
|
task_id="task-download-requested",
|
||||||
|
operation="download",
|
||||||
|
status="requested",
|
||||||
|
source="storage1/docs",
|
||||||
|
destination="docs.zip",
|
||||||
|
created_at="2026-03-10T10:00:00Z",
|
||||||
|
done_items=0,
|
||||||
|
total_items=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._get("/api/tasks/task-download-requested")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
body = response.json()
|
||||||
|
self.assertEqual(body["operation"], "download")
|
||||||
|
self.assertEqual(body["status"], "requested")
|
||||||
|
self.assertEqual(body["done_items"], 0)
|
||||||
|
self.assertEqual(body["total_items"], 1)
|
||||||
|
|
||||||
|
def test_get_task_detail_preparing_archive_download_with_current_item(self) -> None:
|
||||||
|
self._insert_task(
|
||||||
|
task_id="task-download-preparing",
|
||||||
|
operation="download",
|
||||||
|
status="preparing",
|
||||||
|
source="storage1/docs",
|
||||||
|
destination="docs.zip",
|
||||||
|
created_at="2026-03-10T10:00:00Z",
|
||||||
|
started_at="2026-03-10T10:00:01Z",
|
||||||
|
done_items=1,
|
||||||
|
total_items=3,
|
||||||
|
current_item="storage1/docs/b.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._get("/api/tasks/task-download-preparing")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
body = response.json()
|
||||||
|
self.assertEqual(body["operation"], "download")
|
||||||
|
self.assertEqual(body["status"], "preparing")
|
||||||
|
self.assertEqual(body["done_items"], 1)
|
||||||
|
self.assertEqual(body["total_items"], 3)
|
||||||
|
self.assertEqual(body["current_item"], "storage1/docs/b.txt")
|
||||||
|
|
||||||
def test_get_task_detail_cancelled_archive_download(self) -> None:
|
def test_get_task_detail_cancelled_archive_download(self) -> None:
|
||||||
self._insert_task(
|
self._insert_task(
|
||||||
task_id="task-download-cancelled",
|
task_id="task-download-cancelled",
|
||||||
|
|||||||
@@ -229,6 +229,10 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
|||||||
self.assertIn('function closeFeedbackModal()', app_js)
|
self.assertIn('function closeFeedbackModal()', app_js)
|
||||||
self.assertIn('function downloadModalElements()', app_js)
|
self.assertIn('function downloadModalElements()', app_js)
|
||||||
self.assertIn('function isZipDownloadSelection(items)', app_js)
|
self.assertIn('function isZipDownloadSelection(items)', app_js)
|
||||||
|
self.assertIn('function archiveTaskStatusLabel(status)', app_js)
|
||||||
|
self.assertIn('function archiveTaskCountText(task)', app_js)
|
||||||
|
self.assertIn('function archiveTaskCurrentItemText(task)', app_js)
|
||||||
|
self.assertIn('function archiveTaskProgressPercent(task)', app_js)
|
||||||
self.assertIn('function openZipDownloadModal(selectedItems)', app_js)
|
self.assertIn('function openZipDownloadModal(selectedItems)', app_js)
|
||||||
self.assertIn('function markZipDownloadReady(fileName)', app_js)
|
self.assertIn('function markZipDownloadReady(fileName)', app_js)
|
||||||
self.assertIn('function markZipDownloadFailed(err)', app_js)
|
self.assertIn('function markZipDownloadFailed(err)', app_js)
|
||||||
@@ -248,16 +252,18 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
|||||||
self.assertIn('async function downloadFileRequest(paths)', app_js)
|
self.assertIn('async function downloadFileRequest(paths)', app_js)
|
||||||
self.assertIn('const zipDownload = isZipDownloadSelection(selectedItems);', app_js)
|
self.assertIn('const zipDownload = isZipDownloadSelection(selectedItems);', app_js)
|
||||||
self.assertIn('openZipDownloadModal(selectedItems);', app_js)
|
self.assertIn('openZipDownloadModal(selectedItems);', app_js)
|
||||||
self.assertIn('targetText: "Preparing download..."', app_js)
|
self.assertIn('targetText: "Archive download requested"', app_js)
|
||||||
self.assertIn('statusText: "Preparing download..."', app_js)
|
self.assertIn('statusText: "Requested"', app_js)
|
||||||
self.assertIn('countText: "Preparing zip download"', app_js)
|
self.assertIn('countText: "Waiting for archive task"', app_js)
|
||||||
self.assertIn('countText: "Zip preflight and packaging"', app_js)
|
self.assertIn('targetText: "Archive download task"', app_js)
|
||||||
self.assertIn('statusText: "Download started"', app_js)
|
self.assertIn('statusText: "Ready"', app_js)
|
||||||
self.assertIn('countText: "Browser download started"', app_js)
|
self.assertIn('countText: "Browser download requested"', app_js)
|
||||||
self.assertIn('countText: "Zip download failed"', app_js)
|
self.assertIn('countText: "Archive task failed"', app_js)
|
||||||
self.assertIn('countText: "Zip download cancelled"', app_js)
|
self.assertIn('countText: "Archive task cancelled"', app_js)
|
||||||
self.assertIn('statusText: "Cancelling download..."', app_js)
|
self.assertIn('statusText: "Cancelling download..."', app_js)
|
||||||
self.assertIn('statusText: err.message || "Download failed"', app_js)
|
self.assertIn('statusText: `Failed: ${err.message || "Archive download failed"}`', app_js)
|
||||||
|
self.assertIn('return `${task.done_items}/${task.total_items} top-level items`;', app_js)
|
||||||
|
self.assertIn('return `Current: ${task.current_item}`;', app_js)
|
||||||
self.assertIn('downloadProgressState.requestKey === requestKey', app_js)
|
self.assertIn('downloadProgressState.requestKey === requestKey', app_js)
|
||||||
self.assertIn('setStatus("Preparing download...");', app_js)
|
self.assertIn('setStatus("Preparing download...");', app_js)
|
||||||
self.assertIn('"/api/files/download/archive-prepare"', app_js)
|
self.assertIn('"/api/files/download/archive-prepare"', app_js)
|
||||||
|
|||||||
+72
-34
@@ -381,6 +381,59 @@ function selectedItemCountLabel(totalItems) {
|
|||||||
return `${totalItems} selected item${totalItems === 1 ? "" : "s"}`;
|
return `${totalItems} selected item${totalItems === 1 ? "" : "s"}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function archiveTaskStatusLabel(status) {
|
||||||
|
switch (status) {
|
||||||
|
case "requested":
|
||||||
|
return "Requested";
|
||||||
|
case "preparing":
|
||||||
|
return "Preparing";
|
||||||
|
case "ready":
|
||||||
|
return "Ready";
|
||||||
|
case "failed":
|
||||||
|
return "Failed";
|
||||||
|
case "cancelled":
|
||||||
|
return "Cancelled";
|
||||||
|
default:
|
||||||
|
return "Preparing";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function archiveTaskCountText(task) {
|
||||||
|
if (typeof task.done_items === "number" && typeof task.total_items === "number") {
|
||||||
|
return `${task.done_items}/${task.total_items} top-level items`;
|
||||||
|
}
|
||||||
|
if (typeof task.total_items === "number") {
|
||||||
|
return `0/${task.total_items} top-level items`;
|
||||||
|
}
|
||||||
|
if (task.status === "requested") {
|
||||||
|
return "Waiting for archive worker";
|
||||||
|
}
|
||||||
|
return "Preparing archive";
|
||||||
|
}
|
||||||
|
|
||||||
|
function archiveTaskCurrentItemText(task) {
|
||||||
|
if (task.current_item) {
|
||||||
|
return `Current: ${task.current_item}`;
|
||||||
|
}
|
||||||
|
if (task.status === "requested") {
|
||||||
|
return `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`;
|
||||||
|
}
|
||||||
|
if (task.status === "ready") {
|
||||||
|
return `Prepared ${selectedItemCountLabel(downloadProgressState.totalItems)}`;
|
||||||
|
}
|
||||||
|
return `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function archiveTaskProgressPercent(task) {
|
||||||
|
if (task.status === "ready") {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
if (typeof task.done_items === "number" && typeof task.total_items === "number" && task.total_items > 0) {
|
||||||
|
return Math.max(0, Math.min(100, Math.round((task.done_items / task.total_items) * 100)));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function isDownloadModalOpen() {
|
function isDownloadModalOpen() {
|
||||||
return !downloadModalElements().overlay.classList.contains("hidden");
|
return !downloadModalElements().overlay.classList.contains("hidden");
|
||||||
}
|
}
|
||||||
@@ -420,29 +473,14 @@ function openZipDownloadModal(selectedItems) {
|
|||||||
setDownloadModalVisible(true);
|
setDownloadModalVisible(true);
|
||||||
updateDownloadModalDisplay({
|
updateDownloadModalDisplay({
|
||||||
active: true,
|
active: true,
|
||||||
targetText: "Preparing download...",
|
targetText: "Archive download requested",
|
||||||
currentFileText: `Selection: ${selectedItemCountLabel(selectedItems.length)}`,
|
currentFileText: `Selection: ${selectedItemCountLabel(selectedItems.length)}`,
|
||||||
countText: "Preparing zip download",
|
countText: "Waiting for archive task",
|
||||||
statusText: "Preparing download...",
|
statusText: "Requested",
|
||||||
percent: 20,
|
percent: 0,
|
||||||
cancelVisible: true,
|
cancelVisible: true,
|
||||||
cancelDisabled: true,
|
cancelDisabled: true,
|
||||||
});
|
});
|
||||||
requestAnimationFrame(() => {
|
|
||||||
if (!downloadProgressState.active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
updateDownloadModalDisplay({
|
|
||||||
active: true,
|
|
||||||
targetText: "Preparing download...",
|
|
||||||
currentFileText: `Packaging ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
|
||||||
countText: "Zip preflight and packaging",
|
|
||||||
statusText: "Preparing download...",
|
|
||||||
percent: 55,
|
|
||||||
cancelVisible: true,
|
|
||||||
cancelDisabled: !downloadProgressState.taskId || downloadProgressState.cancelRequested,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function markZipDownloadReady(fileName) {
|
function markZipDownloadReady(fileName) {
|
||||||
@@ -451,10 +489,10 @@ function markZipDownloadReady(fileName) {
|
|||||||
downloadProgressState.archiveLabel = fileName || "ZIP archive";
|
downloadProgressState.archiveLabel = fileName || "ZIP archive";
|
||||||
updateDownloadModalDisplay({
|
updateDownloadModalDisplay({
|
||||||
active: false,
|
active: false,
|
||||||
targetText: `Download started: ${downloadProgressState.archiveLabel}`,
|
targetText: `Archive ready: ${downloadProgressState.archiveLabel}`,
|
||||||
currentFileText: `Prepared ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
currentFileText: `Prepared ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
||||||
countText: "Browser download started",
|
countText: "Browser download requested",
|
||||||
statusText: "Download started",
|
statusText: "Ready",
|
||||||
percent: 100,
|
percent: 100,
|
||||||
cancelVisible: false,
|
cancelVisible: false,
|
||||||
});
|
});
|
||||||
@@ -466,10 +504,10 @@ function markZipDownloadFailed(err) {
|
|||||||
downloadProgressState.cancelRequested = false;
|
downloadProgressState.cancelRequested = false;
|
||||||
updateDownloadModalDisplay({
|
updateDownloadModalDisplay({
|
||||||
active: false,
|
active: false,
|
||||||
targetText: "Preparing download...",
|
targetText: "Archive prepare failed",
|
||||||
currentFileText: `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
currentFileText: `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
||||||
countText: "Zip download failed",
|
countText: "Archive task failed",
|
||||||
statusText: err.message || "Download failed",
|
statusText: `Failed: ${err.message || "Archive download failed"}`,
|
||||||
percent: 0,
|
percent: 0,
|
||||||
cancelVisible: false,
|
cancelVisible: false,
|
||||||
});
|
});
|
||||||
@@ -480,10 +518,10 @@ function markZipDownloadCancelled() {
|
|||||||
downloadProgressState.cancelRequested = false;
|
downloadProgressState.cancelRequested = false;
|
||||||
updateDownloadModalDisplay({
|
updateDownloadModalDisplay({
|
||||||
active: false,
|
active: false,
|
||||||
targetText: "Download cancelled",
|
targetText: "Archive prepare cancelled",
|
||||||
currentFileText: `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
currentFileText: `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
||||||
countText: "Zip download cancelled",
|
countText: "Archive task cancelled",
|
||||||
statusText: "Download cancelled",
|
statusText: "Cancelled",
|
||||||
percent: 0,
|
percent: 0,
|
||||||
cancelVisible: false,
|
cancelVisible: false,
|
||||||
});
|
});
|
||||||
@@ -495,11 +533,11 @@ function updateZipDownloadTaskProgress(task) {
|
|||||||
}
|
}
|
||||||
updateDownloadModalDisplay({
|
updateDownloadModalDisplay({
|
||||||
active: true,
|
active: true,
|
||||||
targetText: "Preparing download...",
|
targetText: "Archive download task",
|
||||||
currentFileText: task.current_item ? `Current: ${task.current_item}` : `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
|
currentFileText: archiveTaskCurrentItemText(task),
|
||||||
countText: task.total_items ? `${task.done_items || 0}/${task.total_items} top-level items` : "Preparing zip download",
|
countText: archiveTaskCountText(task),
|
||||||
statusText: downloadProgressState.cancelRequested ? "Cancelling download..." : task.status === "ready" ? "Download started" : "Preparing download...",
|
statusText: downloadProgressState.cancelRequested ? "Cancelling download..." : archiveTaskStatusLabel(task.status),
|
||||||
percent: task.status === "ready" ? 100 : 55,
|
percent: archiveTaskProgressPercent(task),
|
||||||
cancelVisible: true,
|
cancelVisible: true,
|
||||||
cancelDisabled: !downloadProgressState.taskId || downloadProgressState.cancelRequested,
|
cancelDisabled: !downloadProgressState.taskId || downloadProgressState.cancelRequested,
|
||||||
});
|
});
|
||||||
@@ -720,7 +758,7 @@ async function startDownloadSelected() {
|
|||||||
const created = await createArchiveDownloadTask(selectedPaths);
|
const created = await createArchiveDownloadTask(selectedPaths);
|
||||||
downloadProgressState.taskId = created.task_id;
|
downloadProgressState.taskId = created.task_id;
|
||||||
updateZipDownloadTaskProgress({
|
updateZipDownloadTaskProgress({
|
||||||
status: "preparing",
|
status: created.status || "requested",
|
||||||
current_item: null,
|
current_item: null,
|
||||||
done_items: 0,
|
done_items: 0,
|
||||||
total_items: selectedItems.length,
|
total_items: selectedItems.length,
|
||||||
|
|||||||
Reference in New Issue
Block a user