Chunk external object summary requests

This commit is contained in:
Dotta committed 2026-06-23 00:57:36 +00:00
1 parent f6999839c1
commit 517d5522db
2 files changed
+64 -1

No files matched your search

@@ -0,0 +1,48 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExternalObjectSummary } from "@paperclipai/shared";
import { externalObjectsApi } from "../api/externalObjects";
import {
EXTERNAL_OBJECT_SUMMARY_BATCH_SIZE,
fetchIssueExternalObjectSummariesInBatches,
} from "./useIssueExternalObjects";
vi.mock("../api/externalObjects", () => ({
externalObjectsApi: {
getIssueSummaries: vi.fn(),
},
}));
const emptySummary: ExternalObjectSummary = {
total: 0,
byStatusCategory: {},
byLiveness: {},
highestSeverity: "neutral",
objects: [],
};
describe("fetchIssueExternalObjectSummariesInBatches", () => {
afterEach(() => {
vi.mocked(externalObjectsApi.getIssueSummaries).mockReset();
});
it("chunks bulk summary requests below the server validation cap and merges results", async () => {
const issueIds = Array.from(
{ length: EXTERNAL_OBJECT_SUMMARY_BATCH_SIZE * 2 + 3 },
(_entry, index) => `issue-${index}`,
);
vi.mocked(externalObjectsApi.getIssueSummaries).mockImplementation(async (_companyId, ids) => ({
summaries: Object.fromEntries(ids.map((id) => [id, { ...emptySummary, total: id.endsWith("-0") ? 1 : 0 }])),
}));
const result = await fetchIssueExternalObjectSummariesInBatches("company-1", issueIds);
expect(externalObjectsApi.getIssueSummaries).toHaveBeenCalledTimes(3);
expect(vi.mocked(externalObjectsApi.getIssueSummaries).mock.calls.map((call) => call[1].length)).toEqual([
EXTERNAL_OBJECT_SUMMARY_BATCH_SIZE,
EXTERNAL_OBJECT_SUMMARY_BATCH_SIZE,
3,
]);
expect(Object.keys(result.summaries)).toHaveLength(issueIds.length);
expect(result.summaries["issue-0"]?.total).toBe(1);
});
});
+16 -1
View File
@@ -12,6 +12,21 @@ import type { MarkdownExternalReferenceMap } from "../components/MarkdownBody";
import type { ExternalObjectPillData } from "../components/ExternalObjectPill";
import { instanceSettingsApi } from "../api/instanceSettings";
export const EXTERNAL_OBJECT_SUMMARY_BATCH_SIZE = 500;
export async function fetchIssueExternalObjectSummariesInBatches(
companyId: string,
issueIds: readonly string[],
) {
const summaries: Record<string, ExternalObjectSummary> = {};
for (let index = 0; index < issueIds.length; index += EXTERNAL_OBJECT_SUMMARY_BATCH_SIZE) {
const batch = issueIds.slice(index, index + EXTERNAL_OBJECT_SUMMARY_BATCH_SIZE);
const response = await externalObjectsApi.getIssueSummaries(companyId, batch);
Object.assign(summaries, response.summaries);
}
return { summaries };
}
/**
* Browser-side mention-source label. Keep in sync with the shared formatter
* without coupling this hook to the server-only URL canonicalization helpers.
@@ -201,7 +216,7 @@ export function useIssueExternalObjectSummaries(
const enabled = externalObjectsFeature.isEnabled && Boolean(companyId) && normalizedIssueIds.length > 0;
const query = useQuery({
queryKey: queryKeys.externalObjects.issueSummaries(companyId ?? "__none__", normalizedIssueIds),
queryFn: () => externalObjectsApi.getIssueSummaries(companyId!, normalizedIssueIds),
queryFn: () => fetchIssueExternalObjectSummariesInBatches(companyId!, normalizedIssueIds),
enabled,
staleTime: 60_000,
});