From 01dfbb29caa52f264517b4dc25dba98de7fca1f2 Mon Sep 17 00:00:00 2001 From: jesus Date: Tue, 8 Sep 2026 22:42:11 +0000 Subject: [PATCH] fix(ui): label users without an email by user id in key owner picker Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../create_key_button.integration.test.tsx | 32 ++++++++++++++++--- .../organisms/create_key_button.tsx | 4 +-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx index d4cfccf98c8..daf80ffefde 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx @@ -852,7 +852,7 @@ describe("CreateKey", () => { }); it("keeps the current search's users when an abandoned search answers last", async () => { - const answers = new Map void>(); + const answers = new Map void>(); vi.mocked(userFilterUICall).mockImplementation( (_accessToken, params) => new Promise((resolve) => { @@ -883,8 +883,32 @@ describe("CreateKey", () => { expect(screen.getByRole("option", { name: "alice.smith@example.com (u-smith)" })).toBeInTheDocument(); }); + it("labels a user with no email by their user id", async () => { + const answers = new Map void>(); + vi.mocked(userFilterUICall).mockImplementation( + (_accessToken, params) => + new Promise((resolve) => { + answers.set(params.get("search") ?? "", resolve); + }) as never, + ); + + const user = userEvent.setup(); + renderCreateKey({ autoOpenCreate: true, prefillData: { owned_by: "another_user" } }); + const search = await userSearchInput(); + + await user.type(search, "svc"); + await waitFor(() => expect(answers.has("svc")).toBe(true), { timeout: 3000 }); + + await act(async () => { + answers.get("svc")?.([{ user_id: "svc-bot", user_email: null }]); + }); + + expect(await screen.findByRole("option", { name: "svc-bot" })).toBeInTheDocument(); + expect(screen.queryByRole("option", { name: /null/ })).not.toBeInTheDocument(); + }); + it("stops searching once the box is cleared and the abandoned search answers", async () => { - const answers = new Map void>(); + const answers = new Map void>(); vi.mocked(userFilterUICall).mockImplementation( (_accessToken, params) => new Promise((resolve) => { @@ -912,7 +936,7 @@ describe("CreateKey", () => { }); it("keeps searching while a newer search is still in flight", async () => { - const answers = new Map void>(); + const answers = new Map void>(); vi.mocked(userFilterUICall).mockImplementation( (_accessToken, params) => new Promise((resolve) => { @@ -946,7 +970,7 @@ describe("CreateKey", () => { it("only warns about a failed search when it is the one the box is waiting on", async () => { const answers = new Map< string, - { resolve: (users: { user_id: string; user_email: string }[]) => void; reject: (error: Error) => void } + { resolve: (users: { user_id: string; user_email: string | null }[]) => void; reject: (error: Error) => void } >(); vi.mocked(userFilterUICall).mockImplementation( (_accessToken, params) => diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx index 92c35a5b11d..b3fc37988e1 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx @@ -159,7 +159,7 @@ interface CreateKeyProps { interface User { user_id: string; - user_email: string; + user_email: string | null; role?: string; } @@ -577,7 +577,7 @@ const CreateKey: React.FC = ({ team, teams, data, addKey, autoOp const data: User[] = response; const options: SearchSelectOption[] = data.map((user) => ({ - label: `${user.user_email} (${user.user_id})`, + label: user.user_email ? `${user.user_email} (${user.user_id})` : user.user_id, value: user.user_id, }));