From 8aa1ebfb07ca82da4ef38debe2d0739d491a1b6d Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 20 Mar 2026 23:04:25 -0700 Subject: [PATCH] [Fix] UI - Policies: Fix unhandled promise rejections and isPending semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrap mutateAsync calls in try/catch to swallow re-thrown errors (notifications already handled by onError in mutation hooks) - Use isLoading instead of isPending for version loading state — isPending is true when query is disabled with no cache, isLoading is only true during active fetches (matches original behavior) - Add isLoading assertions to disabled-state tests Co-Authored-By: Claude Opus 4.6 --- .../hooks/policies/usePolicyVersions.test.ts | 4 ++ .../policies/pipeline_flow_builder.tsx | 38 ++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) 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 438246eab07..f773614388f 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 @@ -96,6 +96,8 @@ describe("usePolicyVersions", () => { ); expect(result.current.fetchStatus).toBe("idle"); + // isLoading (not isPending) must be false when query is disabled + expect(result.current.isLoading).toBe(false); expect(mockListPolicyVersions).not.toHaveBeenCalled(); }); @@ -106,6 +108,8 @@ describe("usePolicyVersions", () => { ); expect(result.current.fetchStatus).toBe("idle"); + // isLoading (not isPending) must be false when query is disabled + expect(result.current.isLoading).toBe(false); expect(mockListPolicyVersions).not.toHaveBeenCalled(); }); 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 6dc235e459e..6984948271e 100644 --- a/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx +++ b/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx @@ -1305,7 +1305,7 @@ export const FlowBuilderPage: React.FC = ({ const { data: versionsData, - isPending: isVersionsLoading, + isLoading: isVersionsLoading, } = usePolicyVersions({ policyName: editingPolicy?.policy_name, enabled: showVersionsSidebar, @@ -1316,8 +1316,12 @@ export const FlowBuilderPage: React.FC = ({ const updateStatusMutation = useUpdatePolicyVersionStatus(editingPolicy?.policy_name); const handleNewVersion = async () => { - const newPolicy = await createVersionMutation.mutateAsync(); - onVersionCreated?.(newPolicy); + try { + const newPolicy = await createVersionMutation.mutateAsync(); + onVersionCreated?.(newPolicy); + } catch { + // Notification already shown by onError in the mutation hook + } }; const handleSelectVersion = (policy: Policy) => { @@ -1326,20 +1330,28 @@ export const FlowBuilderPage: React.FC = ({ const handlePublishVersion = async () => { if (!editingPolicy?.policy_id) return; - const updated = await updateStatusMutation.mutateAsync({ - policyId: editingPolicy.policy_id, - status: "published", - }); - onVersionStatusUpdated?.(updated); + try { + const updated = await updateStatusMutation.mutateAsync({ + policyId: editingPolicy.policy_id, + status: "published", + }); + onVersionStatusUpdated?.(updated); + } catch { + // Notification already shown by onError in the mutation hook + } }; const handlePromoteToProduction = async () => { if (!editingPolicy?.policy_id) return; - const updated = await updateStatusMutation.mutateAsync({ - policyId: editingPolicy.policy_id, - status: "production", - }); - onVersionStatusUpdated?.(updated); + try { + const updated = await updateStatusMutation.mutateAsync({ + policyId: editingPolicy.policy_id, + status: "production", + }); + onVersionStatusUpdated?.(updated); + } catch { + // Notification already shown by onError in the mutation hook + } }; const handleSave = async () => {