diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.test.ts index beb40742269..ba5792644c0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.test.ts @@ -172,6 +172,22 @@ describe("useCreatePolicyVersion", () => { expect(NotificationsManager.success).toHaveBeenCalledWith("New draft version created"); }); + it("invalidates the versions cache on success", async () => { + mockCreatePolicyVersion.mockResolvedValue({ policy_id: "v3" }); + const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries"); + + const { result } = renderHook( + () => useCreatePolicyVersion("my-policy"), + { wrapper } + ); + + await result.current.mutateAsync(); + + expect(invalidateSpy).toHaveBeenCalledWith({ + queryKey: ["policyVersions", "detail", "my-policy"], + }); + }); + it("shows error notification on failure", async () => { mockCreatePolicyVersion.mockRejectedValue(new Error("Server error")); @@ -249,6 +265,22 @@ describe("useUpdatePolicyVersionStatus", () => { ); }); + it("invalidates the versions cache on success", async () => { + mockUpdatePolicyVersionStatus.mockResolvedValue({ policy_id: "v2" }); + const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries"); + + const { result } = renderHook( + () => useUpdatePolicyVersionStatus("my-policy"), + { wrapper } + ); + + await result.current.mutateAsync({ policyId: "v2", status: "published" }); + + expect(invalidateSpy).toHaveBeenCalledWith({ + queryKey: ["policyVersions", "detail", "my-policy"], + }); + }); + it("promotes to production and shows success notification", async () => { const updatedPolicy = { policy_id: "v2", version_status: "production" }; mockUpdatePolicyVersionStatus.mockResolvedValue(updatedPolicy); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.ts index 9dc78180cdc..95f8fa54eed 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/policies/usePolicyVersions.ts @@ -21,6 +21,13 @@ export interface PolicyVersionsResponse { total_count: number; } +/** Output type after `select` normalizes the response — versions is always defined. */ +export interface PolicyVersionsData { + policy_name: string; + versions: Policy[]; + total_count: number; +} + // ── Fetch function ────────────────────────────────────────────────────────── const fetchPolicyVersions = async ( @@ -43,8 +50,8 @@ export const usePolicyVersions = ({ }: UsePolicyVersionsOptions) => { const { accessToken } = useAuthorized(); - return useQuery({ - queryKey: policyVersionKeys.detail(policyName ?? ""), + return useQuery({ + queryKey: policyVersionKeys.detail(policyName!), queryFn: async () => await fetchPolicyVersions(accessToken!, policyName!), enabled: Boolean(accessToken && policyName && enabled), select: (data) => ({ diff --git a/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx b/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx index 6984948271e..3e2d55207be 100644 --- a/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx +++ b/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx @@ -1310,7 +1310,7 @@ export const FlowBuilderPage: React.FC = ({ policyName: editingPolicy?.policy_name, enabled: showVersionsSidebar, }); - const versions = versionsData?.versions ?? []; + const versions = versionsData?.versions ?? []; // versionsData?.versions is Policy[] after select, fallback covers undefined data const createVersionMutation = useCreatePolicyVersion(editingPolicy?.policy_name); const updateStatusMutation = useUpdatePolicyVersionStatus(editingPolicy?.policy_name);