mirror of
https://github.com/gethomepage/homepage.git
synced 2026-10-15 15:09:56 -07:00
Fix: use x-total-count header for correct gitea totals (#7227)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 (
|
||||
<Container service={service} error={giteaNotificationsError ?? giteaIssuesError ?? giteaRepositoriesError} />
|
||||
);
|
||||
const error = giteaNotificationsError ?? giteaIssuesError ?? giteaPullsError ?? giteaRepositoriesError;
|
||||
if (error) {
|
||||
return <Container service={service} error={error} />;
|
||||
}
|
||||
|
||||
if (!giteaNotifications || !giteaIssues || !giteaRepositories) {
|
||||
if (!giteaNotifications || !giteaIssues || !giteaPulls || !giteaRepositories) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="gitea.notifications" />
|
||||
@@ -28,10 +28,10 @@ export default function Component({ service }) {
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="gitea.notifications" value={giteaNotifications.length} />
|
||||
<Block label="gitea.issues" value={giteaIssues.issues.length} />
|
||||
<Block label="gitea.pulls" value={giteaIssues.pulls.length} />
|
||||
<Block label="gitea.repositories" value={giteaRepositories.data.length} />
|
||||
<Block label="gitea.notifications" value={giteaNotifications.count} />
|
||||
<Block label="gitea.issues" value={giteaIssues.count} />
|
||||
<Block label="gitea.pulls" value={giteaPulls.count} />
|
||||
<Block label="gitea.repositories" value={giteaRepositories.count} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(<Component service={{ widget: { type: "gitea", url: "http://x" } }} />, {
|
||||
@@ -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(<Component service={{ widget: { type: "gitea", url: "http://x" } }} />, {
|
||||
@@ -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(<Component service={{ widget: { type: "gitea", url: "http://x" } }} />, {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
+16
-11
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user