From 5156dbc67f6825b018ea7d030dce85b687ba95fb Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Thu, 17 Sep 2026 17:04:14 -0700 Subject: [PATCH] fix(ui): show the saved team in read mode before the model query refetches Read mode built the Team display from the react-query snapshot while save only wrote localModelData, so the old team flashed until the parent's invalidation refetched. Read the team from localModelData like the neighbouring fields and look up its alias from the local team id. The regression test also waits for the Select trigger to report expanded before clicking an option, because Base UI opens the popup asynchronously and keeps the closed popup mounted, which made back-to-back option clicks flaky. --- .../src/components/ModelInfoEditForm.tsx | 4 +- .../src/components/model_info_view.test.tsx | 38 +++++++++++++++++-- .../src/components/model_info_view.tsx | 6 ++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx b/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx index f0186ff4500..70b9f5598a0 100644 --- a/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx +++ b/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx @@ -818,8 +818,8 @@ const ModelInfoEditForm: React.FC = ({ ) : ( {teamAlias - ? `${teamAlias} (${modelData.model_info.team_id})` - : modelData.model_info.team_id || "Not Set"} + ? `${teamAlias} (${localModelData.model_info?.team_id})` + : localModelData.model_info?.team_id || "Not Set"} )} diff --git a/ui/litellm-dashboard/src/components/model_info_view.test.tsx b/ui/litellm-dashboard/src/components/model_info_view.test.tsx index 7abbcf91fc6..b86bfb0c62f 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.test.tsx @@ -1509,6 +1509,11 @@ describe("ModelInfoView", () => { expect(await screen.findByRole("button", { name: /save changes/i })).toBeInTheDocument(); }; + const openSelect = async (user: ReturnType, triggerText: string) => { + await user.click(await screen.findByText(triggerText)); + await screen.findByRole("combobox", { expanded: true }); + }; + const save = async (user: ReturnType) => { await user.click(screen.getByRole("button", { name: /save changes/i })); await waitFor(() => expect(mockModelPatchUpdateCall).toHaveBeenCalled()); @@ -1660,7 +1665,7 @@ describe("ModelInfoView", () => { const user = userEvent.setup(); await enterEditMode(user); - await user.click(screen.getByText("alpha (team-1)")); + await openSelect(user, "alpha (team-1)"); await user.click(await screen.findByText("beta (team-2)")); const payload = await save(user); @@ -1668,6 +1673,33 @@ describe("ModelInfoView", () => { expect(payload.model_info.team_id).toBe("team-2"); }); + it("shows the picked team in read mode right after saving", async () => { + mockUseTeams.mockReturnValue({ + data: [ + { team_id: "team-1", team_alias: "alpha" }, + { team_id: "team-2", team_alias: "beta" }, + ], + isLoading: false, + error: null, + }); + const teamModel = { + ...defaultModelData, + model_info: { ...defaultModelData.model_info, team_id: "team-1" }, + }; + mockUseModelsInfo.mockReturnValue({ data: { data: [teamModel] }, isLoading: false, error: null }); + mockModelInfoV1Call.mockResolvedValue({ data: [teamModel] }); + const user = userEvent.setup(); + await enterEditMode(user); + + await openSelect(user, "alpha (team-1)"); + await user.click(await screen.findByText("beta (team-2)")); + await save(user); + + expect(await screen.findByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + expect(screen.getByText("beta (team-2)")).toBeInTheDocument(); + expect(screen.queryByText("alpha (team-1)")).not.toBeInTheDocument(); + }); + it("shows the Team ID placeholder for a model with no team", async () => { mockUseTeams.mockReturnValue({ data: [{ team_id: "team-1", team_alias: "alpha" }], @@ -1734,7 +1766,7 @@ describe("ModelInfoView", () => { const user = userEvent.setup(); await enterEditMode(user); - await user.click(await screen.findByText("selected-credential")); + await openSelect(user, "selected-credential"); await user.click(await screen.findByText("other-credential")); const payload = await save(user); @@ -1770,7 +1802,7 @@ describe("ModelInfoView", () => { const user = userEvent.setup(); await enterEditMode(user); - await user.click(screen.getByText("Select existing health check model")); + await openSelect(user, "Select existing health check model"); await user.click(await screen.findByText("openai/gpt-4o")); const payload = await save(user); diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index fa77b3d5739..9400c0e6bf9 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -115,7 +115,9 @@ export default function ModelInfoView({ // Keep modelData variable name for backwards compatibility const modelData = transformedModelData; - const teamAlias = teams?.find((team) => team.team_id === modelData?.model_info?.team_id)?.team_alias || null; + const aliasForTeam = (teamId: string | null | undefined): string | null => + teams?.find((team) => team.team_id === teamId)?.team_alias || null; + const teamAlias = aliasForTeam(modelData?.model_info?.team_id); const rawModelInfoEntries = Object.entries(modelData?.model_info ?? {}).flatMap((entry) => entry[0] === "team_id" && teamAlias ? [entry, ["team_alias", teamAlias]] : [entry], ); @@ -724,7 +726,7 @@ export default function ModelInfoView({