Fixed: 50 sequential API calls on mount is excessive

This commit is contained in:
Alejandro Tapia 2026-03-03 16:03:25 -08:00
parent 383f79f36f
commit 47ff0d1d86

View file

@ -9,12 +9,35 @@ export interface TeamFilterOptions {
}
const FILTER_OPTIONS_PAGE_SIZE = 100; // API max per page
const MAX_PAGES = 50; // Cap at 5000 keys to avoid unbounded fetches
const MAX_PAGES = 10; // Cap at 1000 keys; filter completeness beyond ~500 has diminishing returns
const processKeysIntoOptions = (
keys: Array<Record<string, unknown>>,
keyAliases: Set<string>,
organizationIds: Set<string>,
userMap: Map<string, string>,
) => {
for (const key of keys) {
const alias = key?.key_alias;
if (alias && typeof alias === "string") {
keyAliases.add(alias.trim());
}
const orgId = key?.organization_id;
if (orgId && typeof orgId === "string") {
organizationIds.add(orgId.trim());
}
const userId = key?.user_id;
if (userId && typeof userId === "string") {
const email = (key?.user as { user_email?: string })?.user_email || userId;
userMap.set(userId, email);
}
}
};
/**
* Fetches filter options (key aliases, org IDs, user IDs) from all team keys.
* Paginates through pages to build complete dropdowns. Capped at 50 pages
* (5000 keys) to limit load for very large teams.
* Fetches filter options (key aliases, org IDs, user IDs) from team keys.
* Fetches page 1 first to get totalPages, then batches remaining pages with
* Promise.all. Capped at 10 pages (1000 keys)
*/
export const fetchTeamFilterOptions = async (
accessToken: string | null,
@ -29,46 +52,50 @@ export const fetchTeamFilterOptions = async (
const organizationIds = new Set<string>();
const userMap = new Map<string, string>();
let page = 1;
let totalPages = 1;
// First request: get page 1 and totalPages
const firstResponse = await keyListCall(
accessToken,
null,
teamId,
null,
null,
null,
1,
FILTER_OPTIONS_PAGE_SIZE,
null,
null,
"user",
null,
);
do {
const response = await keyListCall(
accessToken,
null,
teamId,
null,
null,
null,
page,
FILTER_OPTIONS_PAGE_SIZE,
null,
null,
"user",
null,
const firstKeys = firstResponse?.keys || [];
const totalPages = firstResponse?.total_pages ?? 1;
processKeysIntoOptions(firstKeys, keyAliases, organizationIds, userMap);
// Batch fetch remaining pages (2 through min(totalPages, MAX_PAGES)) in parallel
const pagesToFetch = Math.min(totalPages, MAX_PAGES) - 1;
if (pagesToFetch > 0) {
const pagePromises = Array.from({ length: pagesToFetch }, (_, i) =>
keyListCall(
accessToken,
null,
teamId,
null,
null,
null,
i + 2,
FILTER_OPTIONS_PAGE_SIZE,
null,
null,
"user",
null,
),
);
const keys = response?.keys || [];
totalPages = response?.total_pages ?? 1;
for (const key of keys) {
const alias = key?.key_alias;
if (alias && typeof alias === "string") {
keyAliases.add(alias.trim());
}
const orgId = key?.organization_id;
if (orgId && typeof orgId === "string") {
organizationIds.add(orgId.trim());
}
const userId = key?.user_id;
if (userId && typeof userId === "string") {
const email = key?.user?.user_email || userId;
userMap.set(userId, email);
}
const responses = await Promise.all(pagePromises);
for (const response of responses) {
processKeysIntoOptions(response?.keys || [], keyAliases, organizationIds, userMap);
}
page++;
} while (page <= totalPages && page <= MAX_PAGES);
}
return {
keyAliases: Array.from(keyAliases).sort(),