diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx index d14357b5026..fc3ffe72aa6 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx @@ -26,6 +26,7 @@ const SidebarProvider = ({ const [allowAgentsForTeamAdmins, setAllowAgentsForTeamAdmins] = useState(false); const [disableVectorStoresForInternalUsers, setDisableVectorStoresForInternalUsers] = useState(false); const [allowVectorStoresForTeamAdmins, setAllowVectorStoresForTeamAdmins] = useState(false); + const [enablePtuCostAttribution, setEnablePtuCostAttribution] = useState(false); useEffect(() => { const fetchUISettings = async () => { @@ -65,6 +66,10 @@ const SidebarProvider = ({ if (settings?.values?.allow_vector_stores_for_team_admins !== undefined) { setAllowVectorStoresForTeamAdmins(Boolean(settings.values.allow_vector_stores_for_team_admins)); } + + if (settings?.values?.enable_ptu_cost_attribution !== undefined) { + setEnablePtuCostAttribution(Boolean(settings.values.enable_ptu_cost_attribution)); + } } catch (error) { console.error("[SidebarProvider] Failed to fetch UI settings:", error); } @@ -86,6 +91,7 @@ const SidebarProvider = ({ allowAgentsForTeamAdmins={allowAgentsForTeamAdmins} disableVectorStoresForInternalUsers={disableVectorStoresForInternalUsers} allowVectorStoresForTeamAdmins={allowVectorStoresForTeamAdmins} + enablePtuCostAttribution={enablePtuCostAttribution} /> ); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.test.tsx new file mode 100644 index 00000000000..6d742482055 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.test.tsx @@ -0,0 +1,42 @@ +import { renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { useIsPtuCostAttributionEnabled } from "./useIsPtuCostAttributionEnabled"; + +const mockUseUISettings = vi.hoisted(() => vi.fn()); +vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({ + useUISettings: mockUseUISettings, +})); + +describe("useIsPtuCostAttributionEnabled", () => { + it("reads enable_ptu_cost_attribution from /get/ui_settings values (regression: v1 read the wrong endpoint)", () => { + mockUseUISettings.mockReturnValue({ + data: { values: { enable_ptu_cost_attribution: true } }, + isLoading: false, + }); + const { result } = renderHook(() => useIsPtuCostAttributionEnabled()); + expect(result.current.enabled).toBe(true); + expect(result.current.isLoading).toBe(false); + }); + + it("returns enabled=false when the flag is not set in ui_settings values", () => { + mockUseUISettings.mockReturnValue({ data: { values: {} }, isLoading: false }); + const { result } = renderHook(() => useIsPtuCostAttributionEnabled()); + expect(result.current.enabled).toBe(false); + }); + + it("returns enabled=false when ui_settings data is undefined (still loading or missing)", () => { + mockUseUISettings.mockReturnValue({ data: undefined, isLoading: true }); + const { result } = renderHook(() => useIsPtuCostAttributionEnabled()); + expect(result.current.enabled).toBe(false); + expect(result.current.isLoading).toBe(true); + }); + + it("returns enabled=false when the flag value is explicitly false", () => { + mockUseUISettings.mockReturnValue({ + data: { values: { enable_ptu_cost_attribution: false } }, + isLoading: false, + }); + const { result } = renderHook(() => useIsPtuCostAttributionEnabled()); + expect(result.current.enabled).toBe(false); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.ts index 5b115750e21..837eaf4c855 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.ts @@ -1,17 +1,13 @@ import { useMemo } from "react"; -import { ConfigType, useProxyConfig } from "@/app/(dashboard)/hooks/proxyConfig/useProxyConfig"; +import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"; const FLAG_FIELD = "enable_ptu_cost_attribution"; export const useIsPtuCostAttributionEnabled = (): { enabled: boolean; isLoading: boolean } => { - const { data, isLoading } = useProxyConfig(ConfigType.GENERAL_SETTINGS); + const { data, isLoading } = useUISettings(); const enabled = useMemo(() => { - if (!data) { - return false; - } - const entry = data.find((item) => item.field_name === FLAG_FIELD); - return Boolean(entry?.field_value); + return Boolean(data?.values?.[FLAG_FIELD]); }, [data]); return { enabled, isLoading }; diff --git a/ui/litellm-dashboard/src/components/leftnav.test.tsx b/ui/litellm-dashboard/src/components/leftnav.test.tsx index 36c1ade67a7..01de310900f 100644 --- a/ui/litellm-dashboard/src/components/leftnav.test.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.test.tsx @@ -122,6 +122,16 @@ describe("Sidebar (leftnav)", () => { expect(screen.getByText("Chat")).toBeInTheDocument(); }); + it("hides PTU Reservations by default", () => { + renderWithProviders(); + expect(screen.queryByText("PTU Reservations")).not.toBeInTheDocument(); + }); + + it("shows PTU Reservations when enablePtuCostAttribution is true", () => { + renderWithProviders(); + expect(screen.getByText("PTU Reservations")).toBeInTheDocument(); + }); + it("expands a nested tab to reveal its children (Tools > Search Tools)", async () => { renderWithProviders(); diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 81c3f4d4f6a..c96fc48243a 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -93,6 +93,7 @@ interface SidebarProps { allowAgentsForTeamAdmins?: boolean; disableVectorStoresForInternalUsers?: boolean; allowVectorStoresForTeamAdmins?: boolean; + enablePtuCostAttribution?: boolean; } interface MenuItem { @@ -401,6 +402,7 @@ const Sidebar_: React.FC = ({ allowAgentsForTeamAdmins, disableVectorStoresForInternalUsers, allowVectorStoresForTeamAdmins, + enablePtuCostAttribution, }) => { const { userId, accessToken, userRole } = useAuthorized(); const { data: organizations } = useOrganizations(); @@ -452,6 +454,7 @@ const Sidebar_: React.FC = ({ } if (item.key === "projects" && !enableProjectsUI) return false; if (item.key === "chat" && !enableChatUI) return false; + if (item.key === "ptu-reservations" && !enablePtuCostAttribution) return false; if ( !isAdmin && item.key === "agents" &&