diff --git a/ui/litellm-dashboard/src/components/model_hub_table.test.tsx b/ui/litellm-dashboard/src/components/model_hub_table.test.tsx new file mode 100644 index 00000000000..be0b9a113f4 --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_hub_table.test.tsx @@ -0,0 +1,69 @@ +import * as networking from "@/components/networking"; +import { render, screen, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import ModelHubTable from "./model_hub_table"; + +vi.mock("@/components/networking", () => ({ + getUiConfig: vi.fn(), + modelHubPublicModelsCall: vi.fn(), + modelHubCall: vi.fn(), + getConfigFieldSetting: vi.fn(), + getProxyBaseUrl: vi.fn(() => "http://localhost:4000"), + getAgentsList: vi.fn(), + fetchMCPServers: vi.fn(), +})); + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + replace: vi.fn(), + }), +})); + +vi.mock("./public_model_hub", () => ({ + default: () =>
Public Model Hub
, +})); + +describe("ModelHubTable", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render", async () => { + vi.mocked(networking.modelHubCall).mockResolvedValue({ + data: [], + }); + vi.mocked(networking.getConfigFieldSetting).mockResolvedValue({ + field_value: false, + }); + vi.mocked(networking.getAgentsList).mockResolvedValue({ + agents: [], + }); + vi.mocked(networking.fetchMCPServers).mockResolvedValue([]); + + render(); + + await waitFor(() => { + expect(screen.getByText("AI Hub")).toBeInTheDocument(); + }); + }); + + it("should call getUiConfig before modelHubPublicModelsCall when publicPage is true", async () => { + const getUiConfigMock = vi.mocked(networking.getUiConfig); + const modelHubPublicModelsCallMock = vi.mocked(networking.modelHubPublicModelsCall); + + getUiConfigMock.mockResolvedValue({ server_root_path: "/", proxy_base_url: "http://localhost:4000" }); + modelHubPublicModelsCallMock.mockResolvedValue([]); + + render(); + + await waitFor(() => { + expect(getUiConfigMock).toHaveBeenCalled(); + expect(modelHubPublicModelsCallMock).toHaveBeenCalled(); + }); + + const getUiConfigCallOrder = getUiConfigMock.mock.invocationCallOrder[0]; + const modelHubPublicModelsCallOrder = modelHubPublicModelsCallMock.mock.invocationCallOrder[0]; + + expect(getUiConfigCallOrder).toBeLessThan(modelHubPublicModelsCallOrder); + }); +}); diff --git a/ui/litellm-dashboard/src/components/model_hub_table.tsx b/ui/litellm-dashboard/src/components/model_hub_table.tsx index 87a7dd2bd69..7d48bf68aed 100644 --- a/ui/litellm-dashboard/src/components/model_hub_table.tsx +++ b/ui/litellm-dashboard/src/components/model_hub_table.tsx @@ -1,25 +1,32 @@ -import React, { useEffect, useState, useRef, useCallback } from "react"; +import { CopyOutlined } from "@ant-design/icons"; +import { Table as TableInstance } from "@tanstack/react-table"; +import { Badge, Button, Card, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, Title } from "@tremor/react"; +import { Modal } from "antd"; +import { Copy } from "lucide-react"; import { useRouter } from "next/navigation"; -import { modelHubCall, modelHubPublicModelsCall, getAgentsList, getProxyBaseUrl, fetchMCPServers } from "./networking"; -import { getConfigFieldSetting } from "./networking"; -import { ModelDataTable } from "./model_dashboard/table"; -import { modelHubColumns } from "./model_hub_table_columns"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { isAdminRole } from "../utils/roles"; import { agentHubColumns, AgentHubData } from "./agent_hub_table_columns"; -import { mcpHubColumns, MCPServerData } from "./mcp_hub_table_columns"; -import PublicModelHub from "./public_model_hub"; -import MakeModelPublicForm from "./make_model_public_form"; import MakeAgentPublicForm from "./make_agent_public_form"; import MakeMCPPublicForm from "./make_mcp_public_form"; +import MakeModelPublicForm from "./make_model_public_form"; +import { mcpHubColumns, MCPServerData } from "./mcp_hub_table_columns"; +import { ModelDataTable } from "./model_dashboard/table"; import ModelFilters from "./model_filters"; -import UsefulLinksManagement from "./useful_links_management"; -import { Card, Text, Title, Button, Badge, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; -import { Modal } from "antd"; -import { CopyOutlined } from "@ant-design/icons"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { Table as TableInstance } from "@tanstack/react-table"; -import { Copy } from "lucide-react"; -import { isAdminRole } from "../utils/roles"; +import { modelHubColumns } from "./model_hub_table_columns"; import NotificationsManager from "./molecules/notifications_manager"; +import { + fetchMCPServers, + getAgentsList, + getConfigFieldSetting, + getProxyBaseUrl, + getUiConfig, + modelHubCall, + modelHubPublicModelsCall, +} from "./networking"; +import PublicModelHub from "./public_model_hub"; +import UsefulLinksManagement from "./useful_links_management"; interface ModelHubTableProps { accessToken: string | null; @@ -101,6 +108,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, const fetchPublicData = async () => { try { setLoading(true); + await getUiConfig(); const _modelHubData = await modelHubPublicModelsCall(); console.log("ModelHubData:", _modelHubData); console.log("First model structure:", _modelHubData[0]); @@ -384,12 +392,10 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {/* Header with Make Public Button */} {publicPage == false && isAdminRole(userRole || "") && (
- +
)} - + {/* Filters */} @@ -416,9 +422,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {/* Header with Make Public Button */} {publicPage == false && isAdminRole(userRole || "") && (
- +
)} @@ -445,9 +449,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {/* Header with Make Public Button */} {publicPage == false && isAdminRole(userRole || "") && (
- +
)} @@ -836,13 +838,13 @@ print(response.choices[0].message.content)`}
Status: - {selectedMcpServer.status || "unknown"} @@ -864,9 +866,7 @@ print(response.choices[0].message.content)`}
URL:
- - {selectedMcpServer.url} - + {selectedMcpServer.url} copyToClipboard(selectedMcpServer.url)} className="cursor-pointer text-gray-500 hover:text-blue-500 flex-shrink-0" @@ -876,9 +876,7 @@ print(response.choices[0].message.content)`} {selectedMcpServer.command && (
Command: - - {selectedMcpServer.command} - + {selectedMcpServer.command}
)}
@@ -940,31 +938,23 @@ print(response.choices[0].message.content)`}
Created At: - - {new Date(selectedMcpServer.created_at).toLocaleString()} - + {new Date(selectedMcpServer.created_at).toLocaleString()}
Updated At: - - {new Date(selectedMcpServer.updated_at).toLocaleString()} - + {new Date(selectedMcpServer.updated_at).toLocaleString()}
{selectedMcpServer.last_health_check && (
Last Health Check: - - {new Date(selectedMcpServer.last_health_check).toLocaleString()} - + {new Date(selectedMcpServer.last_health_check).toLocaleString()}
)}
{selectedMcpServer.health_check_error && (
Health Check Error: - - {selectedMcpServer.health_check_error} - + {selectedMcpServer.health_check_error}
)}