From 74bc75001b6a5ced05cd37b2d4ca9bb360b6c82e Mon Sep 17 00:00:00 2001 From: jesus Date: Wed, 16 Sep 2026 19:47:38 +0000 Subject: [PATCH] fix(ui): render team_metadata_schema keys as fixed labels Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../MetadataKeyValueFields.test.tsx | 76 ++++++++++++++----- .../MetadataKeyValueFields.tsx | 65 +++++++++++----- 2 files changed, 101 insertions(+), 40 deletions(-) diff --git a/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.test.tsx b/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.test.tsx index 721653c6427..3d9f4d8de15 100644 --- a/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.test.tsx +++ b/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.test.tsx @@ -212,17 +212,41 @@ describe("MetadataKeyValueFields with a declared schema", () => { { key: "app_name", label: "Application Name" }, ]; - it("should prepopulate one ordinary editable pair row per declared key", async () => { + it("should render each declared key as a fixed label with only the value editable", async () => { render(); await waitFor(() => { - expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([ - "cost_center", - "app_name", + expect(screen.getAllByTestId("metadata-schema-label").map((label) => label.textContent)).toEqual([ + "Cost Center", + "Application Name", ]); }); - screen.getAllByPlaceholderText("Key").forEach((input) => expect(input).toBeEnabled()); - expect(screen.getAllByLabelText("Remove key-value pair")).toHaveLength(2); + expect(screen.queryByPlaceholderText("Key")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Remove key-value pair")).not.toBeInTheDocument(); + expect(screen.getByLabelText("Cost Center")).toHaveAttribute("placeholder", "Value"); + }); + + it("should fall back to the key when a declared field has no label", async () => { + render(); + + expect(await screen.findByTestId("metadata-schema-label")).toHaveTextContent("cost_center"); + }); + + it("should treat an existing pair that matches a declared key as fixed too", async () => { + render( + , + ); + + expect(await screen.findByLabelText("Cost Center")).toHaveValue("CC-1001"); + expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual(["region"]); + expect(screen.getAllByLabelText("Remove key-value pair")).toHaveLength(1); }); it("should submit a prepopulated key with its typed value", async () => { @@ -244,9 +268,9 @@ describe("MetadataKeyValueFields with a declared schema", () => { ); await waitFor(() => { - expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([ - "cost_center", - "app_name", + expect(screen.getAllByTestId("metadata-schema-label").map((label) => label.textContent)).toEqual([ + "Cost Center", + "Application Name", ]); }); expect(screen.getAllByPlaceholderText("Value").map((input) => (input as HTMLInputElement).value)).toEqual([ @@ -255,18 +279,32 @@ describe("MetadataKeyValueFields with a declared schema", () => { ]); }); - it("should let the user remove a prepopulated row", async () => { + it("should still let the user add and remove free-form pairs below the declared keys", async () => { const user = userEvent.setup(); - render(); + const onFinish = vi.fn(); + render(); - await screen.findAllByPlaceholderText("Key"); - await user.click(screen.getAllByLabelText("Remove key-value pair")[0]); + await screen.findByTestId("metadata-schema-label"); + await user.click(screen.getByRole("button", { name: /add key-value pair/i })); + fireEvent.change(screen.getByPlaceholderText("Key"), { target: { value: "region" } }); + fireEvent.change(screen.getAllByPlaceholderText("Value")[1], { target: { value: "us" } }); + fireEvent.change(screen.getByLabelText("Cost Center"), { target: { value: "CC-1001" } }); + await user.click(screen.getByRole("button", { name: "Save" })); await waitFor(() => { - expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([ - "app_name", - ]); + expect(onFinish).toHaveBeenCalledWith({ + metadata: [ + { key: "cost_center", value: "CC-1001" }, + { key: "region", value: "us" }, + ], + }); }); + + await user.click(screen.getByLabelText("Remove key-value pair")); + await waitFor(() => { + expect(screen.queryByPlaceholderText("Key")).not.toBeInTheDocument(); + }); + expect(screen.getByTestId("metadata-schema-label")).toBeInTheDocument(); }); it("should show a skeleton instead of the editor while the schema is loading", () => { @@ -283,9 +321,9 @@ describe("MetadataKeyValueFields with a declared schema", () => { rerender(); await waitFor(() => { - expect(screen.getAllByPlaceholderText("Key").map((input) => (input as HTMLInputElement).value)).toEqual([ - "cost_center", - "app_name", + expect(screen.getAllByTestId("metadata-schema-label").map((label) => label.textContent)).toEqual([ + "Cost Center", + "Application Name", ]); }); }); diff --git a/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.tsx b/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.tsx index 1c67d48128f..1d4cdc81adf 100644 --- a/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.tsx +++ b/ui/litellm-dashboard/src/components/common_components/MetadataKeyValueFields.tsx @@ -14,6 +14,7 @@ import { TeamMetadataField } from "@/app/(dashboard)/hooks/teams/useTeamMetadata import { FormField } from "@/components/shared/form/FormField"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; export interface MetadataPair { @@ -87,6 +88,7 @@ const MetadataKeyValueFields = ({ }: MetadataKeyValueFieldsProps) => { const { fields, append, remove } = useFieldArray({ control, name }); const seededRef = useRef(false); + const schemaLabelsByKey = new Map(schemaFields.map((field) => [field.key, field.label || field.key])); useEffect(() => { if (seededRef.current || schemaLoading || schemaFields.length === 0) return; @@ -114,29 +116,50 @@ const MetadataKeyValueFields = ({ return ( <> - {fields.map((field, index) => ( -
- }> - {({ ref, value, ...rest }) => ( - + {fields.map((field, index) => { + const schemaLabel = schemaLabelsByKey.get((field as unknown as Partial).key ?? ""); + return ( +
+ {schemaLabel === undefined ? ( + }> + {({ ref, value, ...rest }) => ( + + )} + + ) : ( + )} - - }> - {({ ref, value, ...rest }) => ( - + }> + {({ ref, value, id, ...rest }) => ( + + )} + + {schemaLabel === undefined && ( + )} - - -
- ))} +
+ ); + })}