memoize columns and derived table data in SpendLogsTable
Some checks failed
Unit Tests: Proxy DB Operations / proxy-db (auth-checks, tests/proxy_unit_tests/test_auth_checks.py tests/proxy_unit_tests/test_user_api_key_auth.py, 20, 8) (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-db (key-generation, tests/proxy_unit_tests/test_key_generate_prisma.py, 30, 0) (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-db (remaining, tests/proxy_unit_tests --ignore=tests/proxy_unit_tests/test_key_generate_prisma.py --ignore=tests/proxy_unit_tests/test_auth_checks.py --ignore=tests/proxy_unit_tests/test_user_api_key_auth.py, 30, 8) (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled

The table's columns array, four-pass data pipeline, and sort-change
handler were all being rebuilt on every parent render. That made every
filter click re-instance all 23 TanStack-Table columns, re-run
filter/reduce/map over all rows, and recreate per-row click closures —
all before the intentional 300ms debounce timer even got a chance to
fire.

Local measurement (40 rows, dev mode):

    filter click → query fires: 1957ms → 1217ms (−38%)

Wrap createColumns in useMemo keyed on sortBy/sortOrder, hoist
onSortChange into a useCallback, and move the searchedLogs /
sessionComposition / sessionRepresentativeMap / filteredData derivations
into a single useMemo keyed on filteredLogs.data + searchTerm.

These were pre-existing issues on main — not regressions from the
hook refactor — but the refactor made them user-visible because the
new query debounce put render cost on the critical path.
This commit is contained in:
Ryan Crabbe 2026-04-20 13:46:40 -07:00
parent 85e8e4d220
commit 4c8eabaca1
No known key found for this signature in database

View file

@ -1,5 +1,5 @@
import moment from "moment";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { SettingOutlined } from "@ant-design/icons";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react";
import { Button } from "antd";
@ -133,75 +133,91 @@ export default function SpendLogsTable({
setCurrentPage(1);
}, [handleFilterResetFromHook]);
const handleSortChange = useCallback(
(newSortBy: LogsSortField, newSortOrder: "asc" | "desc") => {
setSortBy(newSortBy);
setSortOrder(newSortOrder);
setCurrentPage(1);
},
[],
);
const columns = useMemo(
() => createColumns({ sortBy, sortOrder, onSortChange: handleSortChange }),
[sortBy, sortOrder, handleSortChange],
);
const filteredData = useMemo(() => {
const searchedLogs = filteredLogs.data.filter((log) => {
const matchesSearch =
!searchTerm ||
log.request_id.includes(searchTerm) ||
log.model.includes(searchTerm) ||
(log.user && log.user.includes(searchTerm));
// No need for additional filtering since we're now handling this in the API call
return matchesSearch;
});
const sessionCompositionById = searchedLogs.reduce<Record<string, { llm: number; agent: number; mcp: number }>>((acc, log) => {
if (!log.session_id) return acc;
if (!acc[log.session_id]) {
acc[log.session_id] = { llm: 0, agent: 0, mcp: 0 };
}
if (MCP_CALL_TYPES.includes(log.call_type)) {
acc[log.session_id].mcp += 1;
} else if (AGENT_CALL_TYPES.includes(log.call_type)) {
acc[log.session_id].agent += 1;
} else {
acc[log.session_id].llm += 1;
}
return acc;
}, {});
// Build a single-pass map of session_id → representative request_id.
// Prefers an LLM row over an MCP row as the representative.
const sessionRepresentativeMap = new Map<string, { requestId: string; isMcp: boolean }>();
for (const log of searchedLogs) {
if (!log.session_id || (log.session_total_count || 1) <= 1) continue;
const isMcp = MCP_CALL_TYPES.includes(log.call_type);
const existing = sessionRepresentativeMap.get(log.session_id);
if (!existing || (existing.isMcp && !isMcp)) {
sessionRepresentativeMap.set(log.session_id, { requestId: log.request_id, isMcp });
}
}
return (
searchedLogs
.map((log) => {
const sessionComposition = log.session_id ? sessionCompositionById[log.session_id] : undefined;
return {
...log,
request_duration_ms: log.request_duration_ms,
session_llm_count: sessionComposition?.llm ?? undefined,
session_mcp_count: sessionComposition?.mcp ?? undefined,
session_agent_count: sessionComposition?.agent ?? undefined,
onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash),
onSessionClick: (sessionId: string) => {
if (sessionId) {
setSelectedSessionId(sessionId);
setSelectedLog(log);
setIsDrawerOpen(true);
}
},
};
})
// Deduplicate multi-call sessions using the pre-built map (O(1) per row).
.filter((log) => {
if (!log.session_id || (log.session_total_count || 1) <= 1) return true;
return sessionRepresentativeMap.get(log.session_id)?.requestId === log.request_id;
})
);
}, [filteredLogs.data, searchTerm]);
if (!accessToken || !token || !userRole || !userID) {
return null;
}
const searchedLogs = filteredLogs.data.filter((log) => {
const matchesSearch =
!searchTerm ||
log.request_id.includes(searchTerm) ||
log.model.includes(searchTerm) ||
(log.user && log.user.includes(searchTerm));
// No need for additional filtering since we're now handling this in the API call
return matchesSearch;
});
const sessionCompositionById = searchedLogs.reduce<Record<string, { llm: number; agent: number; mcp: number }>>((acc, log) => {
if (!log.session_id) return acc;
if (!acc[log.session_id]) {
acc[log.session_id] = { llm: 0, agent: 0, mcp: 0 };
}
if (MCP_CALL_TYPES.includes(log.call_type)) {
acc[log.session_id].mcp += 1;
} else if (AGENT_CALL_TYPES.includes(log.call_type)) {
acc[log.session_id].agent += 1;
} else {
acc[log.session_id].llm += 1;
}
return acc;
}, {});
// Build a single-pass map of session_id → representative request_id.
// Prefers an LLM row over an MCP row as the representative.
const sessionRepresentativeMap = new Map<string, { requestId: string; isMcp: boolean }>();
for (const log of searchedLogs) {
if (!log.session_id || (log.session_total_count || 1) <= 1) continue;
const isMcp = MCP_CALL_TYPES.includes(log.call_type);
const existing = sessionRepresentativeMap.get(log.session_id);
if (!existing || (existing.isMcp && !isMcp)) {
sessionRepresentativeMap.set(log.session_id, { requestId: log.request_id, isMcp });
}
}
const filteredData =
searchedLogs
.map((log) => {
const sessionComposition = log.session_id ? sessionCompositionById[log.session_id] : undefined;
return {
...log,
request_duration_ms: log.request_duration_ms,
session_llm_count: sessionComposition?.llm ?? undefined,
session_mcp_count: sessionComposition?.mcp ?? undefined,
session_agent_count: sessionComposition?.agent ?? undefined,
onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash),
onSessionClick: (sessionId: string) => {
if (sessionId) {
setSelectedSessionId(sessionId);
setSelectedLog(log);
setIsDrawerOpen(true);
}
},
};
})
// Deduplicate multi-call sessions using the pre-built map (O(1) per row).
.filter((log) => {
if (!log.session_id || (log.session_total_count || 1) <= 1) return true;
return sessionRepresentativeMap.get(log.session_id)?.requestId === log.request_id;
}) || [];
const handleRowClick = (log: LogEntry) => {
// Multi-call session row: open in the same right-side drawer (session mode)
if (log.session_id && (log.session_total_count || 1) > 1) {
@ -278,15 +294,7 @@ export default function SpendLogsTable({
filteredLogs={filteredLogs}
/>
<DataTable
columns={createColumns({
sortBy,
sortOrder,
onSortChange: (newSortBy, newSortOrder) => {
setSortBy(newSortBy);
setSortOrder(newSortOrder);
setCurrentPage(1);
},
})}
columns={columns}
data={filteredData}
onRowClick={handleRowClick}
isLoading={logsQuery.isLoading}