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<string, any>
  so Next.js typecheck accepts it.
- Prettier reformatted ptu_reservation_panel.tsx.
This commit is contained in:
Yucheng Zhu 2026-07-15 09:54:03 -07:00
parent 26c9a92981
commit 06d8b45a91
5 changed files with 17 additions and 25 deletions

View file

@ -27,13 +27,21 @@ export interface PtuReservationListFilters {
export const ptuReservationKeys = createQueryKeys("ptuReservations");
const filtersToListParam = (filters: PtuReservationListFilters): Record<string, string | number> => {
const out: Record<string, string | number> = {};
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<PtuReservationItem[]> => {
const { accessToken } = useAuthorized();
return useQuery<PtuReservationItem[]>({
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);

View file

@ -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";

View file

@ -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();
});

View file

@ -345,14 +345,14 @@ export const generateMetadata = (
exportScope: ExportScope,
spendData: EntitySpendData,
): ExportMetadata => {
const summary: Record<string, any> = {
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;

View file

@ -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<string, any>) => {
try {
return await apiClient.post(`/ptu_reservation/new`, { accessToken, body: { ...formValues } });