diff --git a/src/widgets/radarr/component.jsx b/src/widgets/radarr/component.jsx
index 2df6381da..4f6f12480 100644
--- a/src/widgets/radarr/component.jsx
+++ b/src/widgets/radarr/component.jsx
@@ -1,14 +1,37 @@
import Block from "components/services/widget/block";
import Container from "components/services/widget/container";
import { useTranslation } from "next-i18next/pages";
-import { useCallback } from "react";
import QueueEntry from "../../components/widgets/queue/queueEntry";
import useWidgetAPI from "utils/proxy/use-widget-api";
function getProgress(sizeLeft, size) {
- return sizeLeft === 0 ? 100 : (1 - sizeLeft / size) * 100;
+ if (!Number.isFinite(size) || size <= 0) return 0;
+ return Math.min(100, Math.max(0, (1 - sizeLeft / size) * 100));
+}
+
+function formatDownloadState(downloadState) {
+ switch (downloadState) {
+ case "importBlocked":
+ return "import blocked";
+ case "importPending":
+ return "import pending";
+ case "failedPending":
+ return "failed pending";
+ default:
+ return downloadState;
+ }
+}
+
+function getActivity(status, trackedDownloadState) {
+ const completedStates = ["importBlocked", "importPending", "importing", "failedPending"];
+ const downloadState =
+ status === "completed" && completedStates.includes(trackedDownloadState)
+ ? trackedDownloadState
+ : (status ?? trackedDownloadState);
+
+ return formatDownloadState(downloadState);
}
export default function Component({ service }) {
@@ -19,17 +42,6 @@ export default function Component({ service }) {
const { data: queuedData, error: queuedError } = useWidgetAPI(widget, "queue/status");
const { data: queueDetailsData, error: queueDetailsError } = useWidgetAPI(widget, "queue/details");
- const formatDownloadState = useCallback((downloadState) => {
- switch (downloadState) {
- case "importPending":
- return "import pending";
- case "failedPending":
- return "failed pending";
- default:
- return downloadState;
- }
- }, []);
-
if (moviesError || queuedError || queueDetailsError) {
const finalError = moviesError ?? queuedError ?? queueDetailsError;
return ;
@@ -62,7 +74,7 @@ export default function Component({ service }) {
progress={getProgress(queueEntry.sizeLeft, queueEntry.size)}
timeLeft={queueEntry.timeLeft}
title={moviesData.all.find((entry) => entry.id === queueEntry.movieId)?.title ?? t("radarr.unknown")}
- activity={formatDownloadState(queueEntry.trackedDownloadState)}
+ activity={getActivity(queueEntry.status, queueEntry.trackedDownloadState)}
key={`${queueEntry.movieId}-${queueEntry.sizeLeft}`}
/>
))}
diff --git a/src/widgets/radarr/component.test.jsx b/src/widgets/radarr/component.test.jsx
index 09a5c34b2..43873207c 100644
--- a/src/widgets/radarr/component.test.jsx
+++ b/src/widgets/radarr/component.test.jsx
@@ -10,7 +10,11 @@ const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
vi.mock("../../components/widgets/queue/queueEntry", () => ({
- default: ({ title }) =>
{title}
,
+ default: ({ title, activity, progress }) => (
+
+ {title}
+
+ ),
}));
import Component from "./component";
@@ -37,11 +41,37 @@ describe("widgets/radarr/component", () => {
it("renders counts and queue entries when enabled", () => {
useWidgetAPI.mockImplementation((_widget, endpoint) => {
if (endpoint === "movie")
- return { data: { wanted: 1, missing: 2, have: 3, all: [{ id: 10, title: "Movie" }] }, error: undefined };
+ return {
+ data: {
+ wanted: 1,
+ missing: 2,
+ have: 3,
+ all: [
+ { id: 10, title: "Queued Movie" },
+ { id: 11, title: "Imported Movie" },
+ ],
+ },
+ error: undefined,
+ };
if (endpoint === "queue/status") return { data: { totalCount: 1 }, error: undefined };
if (endpoint === "queue/details")
return {
- data: [{ movieId: 10, sizeLeft: 50, size: 100, timeLeft: "1m", trackedDownloadState: "importPending" }],
+ data: [
+ {
+ movieId: 10,
+ sizeLeft: 0,
+ size: 0,
+ status: "queued",
+ trackedDownloadState: "downloading",
+ },
+ {
+ movieId: 11,
+ sizeLeft: 0,
+ size: 100,
+ status: "completed",
+ trackedDownloadState: "importPending",
+ },
+ ],
error: undefined,
};
return { data: undefined, error: undefined };
@@ -54,6 +84,11 @@ describe("widgets/radarr/component", () => {
expectBlockValue(container, "radarr.missing", 2);
expectBlockValue(container, "radarr.queued", 1);
expectBlockValue(container, "radarr.movies", 3);
- expect(screen.getAllByTestId("queue-entry").map((el) => el.textContent)).toEqual(["Movie"]);
+ const queueEntries = screen.getAllByTestId("queue-entry");
+ expect(queueEntries.map((el) => el.textContent)).toEqual(["Queued Movie", "Imported Movie"]);
+ expect(queueEntries.map((el) => [el.dataset.activity, el.dataset.progress])).toEqual([
+ ["queued", "0"],
+ ["import pending", "100"],
+ ]);
});
});
diff --git a/src/widgets/radarr/widget.js b/src/widgets/radarr/widget.js
index 4f71b8d96..5170325d6 100644
--- a/src/widgets/radarr/widget.js
+++ b/src/widgets/radarr/widget.js
@@ -36,8 +36,8 @@ const widget = {
status: entry.status,
}))
.sort((a, b) => {
- const downloadingA = a.trackedDownloadState === "downloading";
- const downloadingB = b.trackedDownloadState === "downloading";
+ const downloadingA = (a.status ?? a.trackedDownloadState) === "downloading";
+ const downloadingB = (b.status ?? b.trackedDownloadState) === "downloading";
if (downloadingA && !downloadingB) {
return -1;
}
@@ -45,8 +45,8 @@ const widget = {
return 1;
}
- const percentA = a.sizeLeft / a.size;
- const percentB = b.sizeLeft / b.size;
+ const percentA = a.size > 0 ? a.sizeLeft / a.size : 1;
+ const percentB = b.size > 0 ? b.sizeLeft / b.size : 1;
if (percentA < percentB) {
return -1;
}
diff --git a/src/widgets/radarr/widget.test.js b/src/widgets/radarr/widget.test.js
index d111bef27..c7a19f39b 100644
--- a/src/widgets/radarr/widget.test.js
+++ b/src/widgets/radarr/widget.test.js
@@ -1,4 +1,4 @@
-import { describe, it } from "vitest";
+import { describe, expect, it } from "vitest";
import { expectWidgetConfigShape } from "test-utils/widget-config";
@@ -8,4 +8,29 @@ describe("radarr widget config", () => {
it("exports a valid widget config", () => {
expectWidgetConfigShape(widget);
});
+
+ it("sorts active downloads ahead of queued downloads", () => {
+ const queue = widget.mappings["queue/details"].map(
+ Buffer.from(
+ JSON.stringify([
+ {
+ movieId: 1,
+ status: "queued",
+ trackedDownloadState: "downloading",
+ size: 0,
+ sizeleft: 0,
+ },
+ {
+ movieId: 2,
+ status: "downloading",
+ trackedDownloadState: "downloading",
+ size: 100,
+ sizeleft: 50,
+ },
+ ]),
+ ),
+ );
+
+ expect(queue.map((entry) => entry.movieId)).toEqual([2, 1]);
+ });
});
diff --git a/src/widgets/sonarr/component.jsx b/src/widgets/sonarr/component.jsx
index 61451e89b..fc3f9db7a 100644
--- a/src/widgets/sonarr/component.jsx
+++ b/src/widgets/sonarr/component.jsx
@@ -1,14 +1,37 @@
import Block from "components/services/widget/block";
import Container from "components/services/widget/container";
import { useTranslation } from "next-i18next/pages";
-import { useCallback } from "react";
import QueueEntry from "../../components/widgets/queue/queueEntry";
import useWidgetAPI from "utils/proxy/use-widget-api";
function getProgress(sizeLeft, size) {
- return sizeLeft === 0 ? 100 : (1 - sizeLeft / size) * 100;
+ if (!Number.isFinite(size) || size <= 0) return 0;
+ return Math.min(100, Math.max(0, (1 - sizeLeft / size) * 100));
+}
+
+function formatDownloadState(downloadState) {
+ switch (downloadState) {
+ case "importBlocked":
+ return "import blocked";
+ case "importPending":
+ return "import pending";
+ case "failedPending":
+ return "failed pending";
+ default:
+ return downloadState;
+ }
+}
+
+function getActivity(status, trackedDownloadState) {
+ const completedStates = ["importBlocked", "importPending", "importing", "failedPending"];
+ const downloadState =
+ status === "completed" && completedStates.includes(trackedDownloadState)
+ ? trackedDownloadState
+ : (status ?? trackedDownloadState);
+
+ return formatDownloadState(downloadState);
}
function getTitle(queueEntry, seriesData) {
@@ -30,17 +53,6 @@ export default function Component({ service }) {
const { data: seriesData, error: seriesError } = useWidgetAPI(widget, "series");
const { data: queueDetailsData, error: queueDetailsError } = useWidgetAPI(widget, "queue/details");
- const formatDownloadState = useCallback((downloadState) => {
- switch (downloadState) {
- case "importPending":
- return "import pending";
- case "failedPending":
- return "failed pending";
- default:
- return downloadState;
- }
- }, []);
-
if (wantedError || queuedError || seriesError || queueDetailsError) {
const finalError = wantedError ?? queuedError ?? seriesError ?? queueDetailsError;
return ;
@@ -71,7 +83,7 @@ export default function Component({ service }) {
progress={getProgress(queueEntry.sizeLeft, queueEntry.size)}
timeLeft={queueEntry.timeLeft}
title={getTitle(queueEntry, seriesData) ?? t("sonarr.unknown")}
- activity={formatDownloadState(queueEntry.trackedDownloadState)}
+ activity={getActivity(queueEntry.status, queueEntry.trackedDownloadState)}
key={`${queueEntry.seriesId}-${queueEntry.episodeId}`}
/>
))}
diff --git a/src/widgets/sonarr/component.test.jsx b/src/widgets/sonarr/component.test.jsx
index 71dd84bb3..d4a56e698 100644
--- a/src/widgets/sonarr/component.test.jsx
+++ b/src/widgets/sonarr/component.test.jsx
@@ -10,7 +10,11 @@ const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
vi.mock("../../components/widgets/queue/queueEntry", () => ({
- default: ({ title }) => {title}
,
+ default: ({ title, activity, progress }) => (
+
+ {title}
+
+ ),
}));
import Component from "./component";
@@ -44,10 +48,19 @@ describe("widgets/sonarr/component", () => {
{
seriesId: 10,
episodeId: 1,
- episodeTitle: "Ep",
- sizeLeft: 50,
+ episodeTitle: "Queued Ep",
+ sizeLeft: 0,
+ size: 0,
+ status: "queued",
+ trackedDownloadState: "downloading",
+ },
+ {
+ seriesId: 10,
+ episodeId: 2,
+ episodeTitle: "Imported Ep",
+ sizeLeft: 0,
size: 100,
- timeLeft: "1m",
+ status: "completed",
trackedDownloadState: "importPending",
},
],
@@ -63,6 +76,11 @@ describe("widgets/sonarr/component", () => {
expectBlockValue(container, "sonarr.wanted", 1);
expectBlockValue(container, "sonarr.queued", 2);
expectBlockValue(container, "sonarr.series", 1);
- expect(screen.getAllByTestId("queue-entry").map((el) => el.textContent)).toEqual(["Show: Ep"]);
+ const queueEntries = screen.getAllByTestId("queue-entry");
+ expect(queueEntries.map((el) => el.textContent)).toEqual(["Show: Queued Ep", "Show: Imported Ep"]);
+ expect(queueEntries.map((el) => [el.dataset.activity, el.dataset.progress])).toEqual([
+ ["queued", "0"],
+ ["import pending", "100"],
+ ]);
});
});
diff --git a/src/widgets/sonarr/widget.js b/src/widgets/sonarr/widget.js
index 1fcef8eb5..ad12bd567 100644
--- a/src/widgets/sonarr/widget.js
+++ b/src/widgets/sonarr/widget.js
@@ -38,8 +38,8 @@ const widget = {
status: entry.status,
}))
.sort((a, b) => {
- const downloadingA = a.trackedDownloadState === "downloading";
- const downloadingB = b.trackedDownloadState === "downloading";
+ const downloadingA = (a.status ?? a.trackedDownloadState) === "downloading";
+ const downloadingB = (b.status ?? b.trackedDownloadState) === "downloading";
if (downloadingA && !downloadingB) {
return -1;
}
@@ -47,8 +47,8 @@ const widget = {
return 1;
}
- const percentA = a.sizeLeft / a.size;
- const percentB = b.sizeLeft / b.size;
+ const percentA = a.size > 0 ? a.sizeLeft / a.size : 1;
+ const percentB = b.size > 0 ? b.sizeLeft / b.size : 1;
if (percentA < percentB) {
return -1;
}
diff --git a/src/widgets/sonarr/widget.test.js b/src/widgets/sonarr/widget.test.js
index a09895874..458535cfa 100644
--- a/src/widgets/sonarr/widget.test.js
+++ b/src/widgets/sonarr/widget.test.js
@@ -1,4 +1,4 @@
-import { describe, it } from "vitest";
+import { describe, expect, it } from "vitest";
import { expectWidgetConfigShape } from "test-utils/widget-config";
@@ -8,4 +8,29 @@ describe("sonarr widget config", () => {
it("exports a valid widget config", () => {
expectWidgetConfigShape(widget);
});
+
+ it("sorts active downloads ahead of queued downloads", () => {
+ const queue = widget.mappings["queue/details"].map(
+ Buffer.from(
+ JSON.stringify([
+ {
+ episodeId: 1,
+ status: "queued",
+ trackedDownloadState: "downloading",
+ size: 0,
+ sizeleft: 0,
+ },
+ {
+ episodeId: 2,
+ status: "downloading",
+ trackedDownloadState: "downloading",
+ size: 100,
+ sizeleft: 50,
+ },
+ ]),
+ ),
+ );
+
+ expect(queue.map((entry) => entry.episodeId)).toEqual([2, 1]);
+ });
});