From b69f3601f87c655d5e64fb19e3a8c31a7660c969 Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Wed, 15 Jul 2026 14:11:08 -0700 Subject: [PATCH] fix(ptu-ui): read flag from ui_settings, gate leftnav on it The useIsPtuCostAttributionEnabled hook was reading /config/list?config_type=general_settings, which is built from the fixed ConfigGeneralSettings schema and does not contain enable_ptu_cost_attribution. The flag lives in the ui_settings surface (/get/ui_settings) and is synced into the in-process general_settings dict at runtime. Point the hook at useUISettings so the panel and Usage Flat Cost column reflect the flag state. Thread enablePtuCostAttribution through SidebarProvider so the PTU Reservations nav item appears and disappears with the flag, matching the enable_chat_ui / enable_projects_ui pattern. --- .../components/SidebarProvider.tsx | 6 +++ .../useIsPtuCostAttributionEnabled.test.tsx | 42 +++++++++++++++++++ .../useIsPtuCostAttributionEnabled.ts | 10 ++--- .../src/components/leftnav.test.tsx | 10 +++++ .../src/components/leftnav.tsx | 3 ++ 5 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled.test.tsx 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" &&