From 06d8b45a91f080e20bcb7a936f04ae4b2e0b107d Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Wed, 15 Jul 2026 09:54:03 -0700 Subject: [PATCH] fix(ptu-ui): CI + greptile p1/p2 - align JSON metadata gating; typecheck; dead code - generateMetadata now gates total_flat_cost/total_cost on hasFlatCost(spendData) same as generateDailyData, so CSV and JSON stay consistent for the same request. Addresses greptile P1 on #33302. - Removes ptuReservationInfoCall from networking.tsx; unused in this stack and greptile flagged it as dead code (P2). - Fixes 'Type PtuReservationListFilters has no properties in common with ListParams' next.js build error by adapting the filters shape to the createQueryKeys ListParams contract. - Types summary as ExportMetadata['summary'] instead of Record so Next.js typecheck accepts it. - Prettier reformatted ptu_reservation_panel.tsx. --- .../hooks/ptuReservations/usePtuReservations.ts | 10 +++++++++- .../_components/ptu_reservation_panel.tsx | 5 +---- .../src/components/EntityUsageExport/utils.test.ts | 14 +++++--------- .../src/components/EntityUsageExport/utils.ts | 4 ++-- ui/litellm-dashboard/src/components/networking.tsx | 9 --------- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/usePtuReservations.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/usePtuReservations.ts index 6cdf14daf87..c8446d617d8 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/usePtuReservations.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/ptuReservations/usePtuReservations.ts @@ -27,13 +27,21 @@ export interface PtuReservationListFilters { export const ptuReservationKeys = createQueryKeys("ptuReservations"); +const filtersToListParam = (filters: PtuReservationListFilters): Record => { + const out: Record = {}; + if (filters.team_id) out.team_id = filters.team_id; + if (filters.model) out.model = filters.model; + if (filters.active_only) out.active_only = "true"; + return out; +}; + export const usePtuReservations = ( filters: PtuReservationListFilters = {}, options: { enabled?: boolean } = {}, ): UseQueryResult => { const { accessToken } = useAuthorized(); return useQuery({ - queryKey: ptuReservationKeys.list(filters), + queryKey: ptuReservationKeys.list({ filters: filtersToListParam(filters) }), queryFn: async () => { const data = await ptuReservationListCall(accessToken!, filters); return (data ?? []).filter((item: PtuReservationItem | null): item is PtuReservationItem => item != null); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/ptu-reservations/_components/ptu_reservation_panel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/ptu-reservations/_components/ptu_reservation_panel.tsx index e769cd44e08..9f3ae293d26 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/ptu-reservations/_components/ptu_reservation_panel.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/ptu-reservations/_components/ptu_reservation_panel.tsx @@ -3,10 +3,7 @@ import { Button, Card, Tag, Typography } from "antd"; import TableIconActionButton from "@/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton"; import { MoneyCell } from "@/components/shared/table_cells"; import { useIsPtuCostAttributionEnabled } from "@/app/(dashboard)/hooks/ptuReservations/useIsPtuCostAttributionEnabled"; -import { - PtuReservationItem, - usePtuReservations, -} from "@/app/(dashboard)/hooks/ptuReservations/usePtuReservations"; +import { PtuReservationItem, usePtuReservations } from "@/app/(dashboard)/hooks/ptuReservations/usePtuReservations"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { isProxyAdminRole } from "@/utils/roles"; import PtuReservationModal from "./ptu_reservation_modal"; diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts index b519483771b..06c7352cdd7 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts @@ -1862,22 +1862,18 @@ describe("EntityUsageExport utils", () => { expect(result.summary.total_tokens).toBe(4500); }); - it("should include total_flat_cost and total_cost in team summary when provided", () => { - const teamSpendWithFlat: EntitySpendData = { + it("should include total_flat_cost and total_cost in summary when total_flat_cost is present", () => { + const spendWithFlat: EntitySpendData = { ...mockSpendData, metadata: { ...mockSpendData.metadata, total_flat_cost: 6.45 }, }; - const result = generateMetadata("team", mockDateRange, [], "daily", teamSpendWithFlat); + const result = generateMetadata("team", mockDateRange, [], "daily", spendWithFlat); expect(result.summary.total_flat_cost).toBeCloseTo(6.45, 4); expect(result.summary.total_cost).toBeCloseTo(46.0 + 6.45, 4); }); - it("should omit total_flat_cost and total_cost from non-team summary", () => { - const teamSpendWithFlat: EntitySpendData = { - ...mockSpendData, - metadata: { ...mockSpendData.metadata, total_flat_cost: 6.45 }, - }; - const result = generateMetadata("user", mockDateRange, [], "daily", teamSpendWithFlat); + it("should omit total_flat_cost and total_cost when total_flat_cost is absent", () => { + const result = generateMetadata("team", mockDateRange, [], "daily", mockSpendData); expect(result.summary.total_flat_cost).toBeUndefined(); expect(result.summary.total_cost).toBeUndefined(); }); diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts index 693736f00d5..0ba7c982d73 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts @@ -345,14 +345,14 @@ export const generateMetadata = ( exportScope: ExportScope, spendData: EntitySpendData, ): ExportMetadata => { - const summary: Record = { + const summary: ExportMetadata["summary"] = { total_spend: spendData.metadata.total_spend, total_requests: spendData.metadata.total_api_requests, successful_requests: spendData.metadata.total_successful_requests, failed_requests: spendData.metadata.total_failed_requests, total_tokens: spendData.metadata.total_tokens, }; - if (entityType === "team") { + if (hasFlatCost(spendData)) { const flatCost = spendData.metadata.total_flat_cost ?? 0; summary.total_flat_cost = flatCost; summary.total_cost = spendData.metadata.total_spend + flatCost; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 43679f723a6..4c6a2004851 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -663,15 +663,6 @@ export const ptuReservationListCall = async ( } }; -export const ptuReservationInfoCall = async (accessToken: string, id: string) => { - try { - return await apiClient.get(`/ptu_reservation/info?id=${encodeURIComponent(id)}`, { accessToken }); - } catch (error) { - console.error("Failed to fetch PTU reservation:", error); - throw error; - } -}; - export const ptuReservationCreateCall = async (accessToken: string, formValues: Record) => { try { return await apiClient.post(`/ptu_reservation/new`, { accessToken, body: { ...formValues } });