diff --git a/src/utils/proxy/handlers/generic.js b/src/utils/proxy/handlers/generic.js index e7653c708..8ee1c41dd 100644 --- a/src/utils/proxy/handlers/generic.js +++ b/src/utils/proxy/handlers/generic.js @@ -49,7 +49,7 @@ export default async function genericProxyHandler(req, res, map) { } } - const [status, contentType, data] = await httpProxy(url, params); + const [status, contentType, data, responseHeaders] = await httpProxy(url, params); let resultData = data; @@ -63,7 +63,7 @@ export default async function genericProxyHandler(req, res, map) { .status(status) .json({ error: { message: "Invalid data", url: sanitizeErrorURL(url), data: resultData } }); } - if (map) resultData = map(resultData); + if (map) resultData = map(resultData, responseHeaders); } if (contentType) res.setHeader("Content-Type", contentType); diff --git a/src/utils/proxy/handlers/generic.test.js b/src/utils/proxy/handlers/generic.test.js index 7edca813d..14c622030 100644 --- a/src/utils/proxy/handlers/generic.test.js +++ b/src/utils/proxy/handlers/generic.test.js @@ -253,4 +253,20 @@ describe("utils/proxy/handlers/generic", () => { expect(res.statusCode).toBe(200); expect(res.body).toEqual({ mapped: true }); }); + + it("passes the response headers to the mapping function", async () => { + getServiceWidget.mockResolvedValue({ + type: "testservice", + url: "http://example", + }); + httpProxy.mockResolvedValueOnce([200, "application/json", { ok: true }, { "x-total-count": "42" }]); + + const req = { method: "GET", query: { group: "g", service: "svc", endpoint: "api", index: "0" } }; + const res = createMockRes(); + + await genericProxyHandler(req, res, (data, headers) => ({ total: headers["x-total-count"] })); + + expect(res.statusCode).toBe(200); + expect(res.body).toEqual({ total: "42" }); + }); }); diff --git a/src/widgets/gitea/component.jsx b/src/widgets/gitea/component.jsx index 3b0b078fd..8cc688c4f 100644 --- a/src/widgets/gitea/component.jsx +++ b/src/widgets/gitea/component.jsx @@ -7,15 +7,15 @@ export default function Component({ service }) { const { data: giteaNotifications, error: giteaNotificationsError } = useWidgetAPI(widget, "notifications"); const { data: giteaIssues, error: giteaIssuesError } = useWidgetAPI(widget, "issues"); + const { data: giteaPulls, error: giteaPullsError } = useWidgetAPI(widget, "pulls"); const { data: giteaRepositories, error: giteaRepositoriesError } = useWidgetAPI(widget, "repositories"); - if (giteaNotificationsError || giteaIssuesError || giteaRepositoriesError) { - return ( - - ); + const error = giteaNotificationsError ?? giteaIssuesError ?? giteaPullsError ?? giteaRepositoriesError; + if (error) { + return ; } - if (!giteaNotifications || !giteaIssues || !giteaRepositories) { + if (!giteaNotifications || !giteaIssues || !giteaPulls || !giteaRepositories) { return ( @@ -28,10 +28,10 @@ export default function Component({ service }) { return ( - - - - + + + + ); } diff --git a/src/widgets/gitea/component.test.jsx b/src/widgets/gitea/component.test.jsx index c0325bba1..13c4167ed 100644 --- a/src/widgets/gitea/component.test.jsx +++ b/src/widgets/gitea/component.test.jsx @@ -20,6 +20,7 @@ describe("widgets/gitea/component", () => { useWidgetAPI .mockReturnValueOnce({ data: undefined, error: undefined }) // notifications .mockReturnValueOnce({ data: undefined, error: undefined }) // issues + .mockReturnValueOnce({ data: undefined, error: undefined }) // pulls .mockReturnValueOnce({ data: undefined, error: undefined }); // repositories const { container } = renderWithProviders(, { @@ -38,6 +39,7 @@ describe("widgets/gitea/component", () => { useWidgetAPI .mockReturnValueOnce({ data: undefined, error: undefined }) .mockReturnValueOnce({ data: undefined, error: { message: "nope" } }) + .mockReturnValueOnce({ data: undefined, error: undefined }) .mockReturnValueOnce({ data: undefined, error: undefined }); renderWithProviders(, { @@ -48,22 +50,20 @@ describe("widgets/gitea/component", () => { expect(screen.getByText("nope")).toBeInTheDocument(); }); - it("renders computed counts when loaded", () => { + it("renders total counts when loaded", () => { useWidgetAPI - .mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }], error: undefined }) - .mockReturnValueOnce({ - data: { issues: [{ id: 1 }], pulls: [{ id: 1 }, { id: 2 }, { id: 3 }] }, - error: undefined, - }) - .mockReturnValueOnce({ data: { data: [{ id: 1 }] }, error: undefined }); + .mockReturnValueOnce({ data: { count: 2 }, error: undefined }) + .mockReturnValueOnce({ data: { count: 1234 }, error: undefined }) + .mockReturnValueOnce({ data: { count: 0 }, error: undefined }) + .mockReturnValueOnce({ data: { count: 56 }, error: undefined }); const { container } = renderWithProviders(, { settings: { hideErrors: false }, }); expectBlockValue(container, "gitea.notifications", 2); - expectBlockValue(container, "gitea.issues", 1); - expectBlockValue(container, "gitea.pulls", 3); - expectBlockValue(container, "gitea.repositories", 1); + expectBlockValue(container, "gitea.issues", 1234); + expectBlockValue(container, "gitea.pulls", 0); + expectBlockValue(container, "gitea.repositories", 56); }); }); diff --git a/src/widgets/gitea/widget.js b/src/widgets/gitea/widget.js index 003a6710d..2d7261a80 100644 --- a/src/widgets/gitea/widget.js +++ b/src/widgets/gitea/widget.js @@ -1,26 +1,31 @@ -import { asJson } from "utils/proxy/api-helpers"; import genericProxyHandler from "utils/proxy/handlers/generic"; +// Get total from header to avoid pagination limit +export function totalCount(data, headers) { + const count = parseInt(headers?.["x-total-count"], 10); + return { count: Number.isNaN(count) ? null : count }; +} + const widget = { api: "{url}/api/v1/{endpoint}?access_token={key}", proxyHandler: genericProxyHandler, mappings: { notifications: { - endpoint: "notifications", + endpoint: "notifications?limit=1", + map: totalCount, }, issues: { - endpoint: "repos/issues/search", - map: (data) => { - const items = asJson(data); - return { - pulls: items.filter((issue) => issue.pull_request), - issues: items.filter((issue) => !issue.pull_request), - }; - }, + endpoint: "repos/issues/search?type=issues&limit=1", + map: totalCount, + }, + pulls: { + endpoint: "repos/issues/search?type=pulls&limit=1", + map: totalCount, }, repositories: { - endpoint: "repos/search", + endpoint: "repos/search?limit=1", + map: totalCount, }, }, }; diff --git a/src/widgets/gitea/widget.test.js b/src/widgets/gitea/widget.test.js index 1039ad7ce..525e91058 100644 --- a/src/widgets/gitea/widget.test.js +++ b/src/widgets/gitea/widget.test.js @@ -2,19 +2,20 @@ import { describe, expect, it } from "vitest"; import { expectWidgetConfigShape } from "test-utils/widget-config"; -import widget from "./widget"; +import widget, { totalCount } from "./widget"; describe("gitea widget config", () => { it("exports a valid widget config", () => { expectWidgetConfigShape(widget); }); - it("splits issues and pull requests", () => { - const result = widget.mappings.issues.map( - Buffer.from(JSON.stringify([{ id: 1, pull_request: {} }, { id: 2 }, { id: 3 }])), - ); + it("reads the total count from the X-Total-Count header", () => { + expect(totalCount(Buffer.from("[]"), { "x-total-count": "1234" })).toEqual({ count: 1234 }); + expect(totalCount(Buffer.from("[]"), { "x-total-count": "0" })).toEqual({ count: 0 }); + }); - expect(result.pulls.map((i) => i.id)).toEqual([1]); - expect(result.issues.map((i) => i.id)).toEqual([2, 3]); + it("returns a null count when the header is missing", () => { + expect(totalCount(Buffer.from("[{}]"), {})).toEqual({ count: null }); + expect(totalCount(Buffer.from("[{}]"), undefined)).toEqual({ count: null }); }); });