fix: Add array type checks for model, agent, and MCP hub data to prevent crashes from non-array API responses and include a regression test.

This PR:Fixes a frontend regression where the
PublicModelHub
 page would crash with TypeError: e.filter is not a function when the API returned an error object (e.g. { "detail": "..." }) instead of the expected data array.
Changes:

Added defensive Array.isArray() checks in
src/components/public_model_hub.tsx
 for:
modelHubData
agentHubData
mcpHubData
Updated useMemo hooks and helper functions to handle invalid data gracefully.
Added a regression test in
src/components/public_model_hub.test.tsx
 that mocks a non-array API response to ensure the component renders without crashing.
This commit is contained in:
Swayambhu 2026-02-05 10:28:04 +05:30
parent 02ed25c886
commit 2c430ebd96
2 changed files with 51 additions and 21 deletions

View file

@ -38,10 +38,10 @@ beforeAll(() => {
matches: false,
media: query,
onchange: null,
addListener: () => { },
removeListener: () => { },
addEventListener: () => { },
removeEventListener: () => { },
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => false,
}),
});
@ -171,4 +171,18 @@ describe("PublicModelHub", () => {
expect(unknownStatus).toBeInTheDocument();
});
});
it("handles non-array response gracefully (regression test for e.filter crash)", async () => {
const networkingModule = await import("./networking");
// Mock the API to return an object (like an error response) instead of an array
vi.mocked(networkingModule.modelHubPublicModelsCall).mockResolvedValue({
detail: "No models configured",
} as any);
render(<PublicModelHub />);
await waitFor(() => {
expect(screen.getByTestId("navbar")).toBeInTheDocument();
expect(screen.getByText("Model Hub")).toBeInTheDocument();
});
});
});

View file

@ -250,7 +250,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
};
const filteredData = useMemo(() => {
if (!modelHubData) return [];
if (!modelHubData || !Array.isArray(modelHubData)) return [];
let searchResults = modelHubData;
@ -324,7 +324,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
}, [modelHubData, searchTerm, selectedProviders, selectedModes, selectedFeatures]);
const filteredAgentData = useMemo(() => {
if (!agentHubData) return [];
if (!agentHubData || !Array.isArray(agentHubData)) return [];
let searchResults = agentHubData;
@ -375,7 +375,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
}, [agentHubData, agentSearchTerm, selectedAgentSkills]);
const filteredMcpData = useMemo(() => {
if (!mcpHubData) return [];
if (!mcpHubData || !Array.isArray(mcpHubData)) return [];
let searchResults = mcpHubData;
@ -696,18 +696,29 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
enableSorting: true,
cell: ({ row }) => {
const original = row.original;
const tagColor = original.health_status === "healthy" ? "green" : original.health_status === "unhealthy" ? "red" : "default";
const responseTimeLabel = original.health_response_time ? `Response Time: ${Number(original.health_response_time).toFixed(2)}ms` : "N/A";
const lastCheckedLabel = original.health_checked_at ? `Last Checked: ${new Date(original.health_checked_at).toLocaleString()}` : "N/A";
const tagColor =
original.health_status === "healthy" ? "green" : original.health_status === "unhealthy" ? "red" : "default";
const responseTimeLabel = original.health_response_time
? `Response Time: ${Number(original.health_response_time).toFixed(2)}ms`
: "N/A";
const lastCheckedLabel = original.health_checked_at
? `Last Checked: ${new Date(original.health_checked_at).toLocaleString()}`
: "N/A";
return <Tooltip title={<>
<div>
{responseTimeLabel}
</div>
<div>
{lastCheckedLabel}
</div>
</>}><Tag key={original.model_group} color={tagColor}><span className="capitalize">{original.health_status ?? "Unknown"}</span></Tag></Tooltip>;
return (
<Tooltip
title={
<>
<div>{responseTimeLabel}</div>
<div>{lastCheckedLabel}</div>
</>
}
>
<Tag key={original.model_group} color={tagColor}>
<span className="capitalize">{original.health_status ?? "Unknown"}</span>
</Tag>
</Tooltip>
);
},
size: 100,
},
@ -963,7 +974,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
accessToken={accessToken || null}
isPublicPage={true}
isDarkMode={false}
toggleDarkMode={() => { }}
toggleDarkMode={() => {}}
/>
)}
@ -1092,6 +1103,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
}}
>
{modelHubData &&
Array.isArray(modelHubData) &&
getUniqueProviders(modelHubData).map((provider) => (
<Select.Option key={provider} value={provider}>
{provider}
@ -1111,6 +1123,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
allowClear
>
{modelHubData &&
Array.isArray(modelHubData) &&
getUniqueModes(modelHubData).map((mode) => (
<Select.Option key={mode} value={mode}>
{mode}
@ -1130,6 +1143,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
allowClear
>
{modelHubData &&
Array.isArray(modelHubData) &&
getUniqueFeatures(modelHubData).map((feature) => (
<Select.Option key={feature} value={feature}>
{feature}
@ -1154,7 +1168,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
</TabPane>
{/* Agents Tab */}
{agentHubData && agentHubData.length > 0 && (
{agentHubData && Array.isArray(agentHubData) && agentHubData.length > 0 && (
<TabPane tab="Agent Hub" key="agents">
<div className="flex justify-between items-center mb-8">
<Title className="text-2xl font-semibold text-gray-900">Available Agents</Title>
@ -1192,6 +1206,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
allowClear
>
{agentHubData &&
Array.isArray(agentHubData) &&
getUniqueAgentSkills(agentHubData).map((skill) => (
<Select.Option key={skill} value={skill}>
{skill}
@ -1217,7 +1232,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
)}
{/* MCP Servers Tab */}
{mcpHubData && mcpHubData.length > 0 && (
{mcpHubData && Array.isArray(mcpHubData) && mcpHubData.length > 0 && (
<TabPane tab="MCP Hub" key="mcp">
<div className="flex justify-between items-center mb-8">
<Title className="text-2xl font-semibold text-gray-900">Available MCP Servers</Title>
@ -1255,6 +1270,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
allowClear
>
{mcpHubData &&
Array.isArray(mcpHubData) &&
getUniqueMcpTransports(mcpHubData).map((transport) => (
<Select.Option key={transport} value={transport}>
{transport}