Merge pull request #19119 from BerriAI/litellm_ui_key_edit_team_dropdown

[Feature] UI - Edit Key: Add search to key edit team dropdown
This commit is contained in:
yuneng-jiang 2026-01-14 16:17:14 -08:00 • committed by GitHub
commit 93f3dd8008
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 58 additions and 41 deletions

View file

@ -29,34 +29,33 @@ import { coy } from "react-syntax-highlighter/dist/esm/styles/prism";
import { v4 as uuidv4 } from "uuid";
import { truncateString } from "../../../utils/textUtils";
import GuardrailSelector from "../../guardrails/GuardrailSelector";
import { MCPServer } from "../../mcp_tools/types";
import NotificationsManager from "../../molecules/notifications_manager";
import { fetchMCPServers, listMCPTools } from "../../networking";
import TagSelector from "../../tag_management/TagSelector";
import VectorStoreSelector from "../../vector_store_management/VectorStoreSelector";
import { makeA2ASendMessageRequest } from "../llm_calls/a2a_send_message";
import { makeAnthropicMessagesRequest } from "../llm_calls/anthropic_messages";
import { makeOpenAIAudioSpeechRequest } from "../llm_calls/audio_speech";
import { makeOpenAIAudioTranscriptionRequest } from "../llm_calls/audio_transcriptions";
import { makeOpenAIChatCompletionRequest } from "../llm_calls/chat_completion";
import { makeOpenAIEmbeddingsRequest } from "../llm_calls/embeddings_api";
import { Agent, fetchAvailableAgents } from "../llm_calls/fetch_agents";
import { fetchAvailableModels, ModelGroup } from "../llm_calls/fetch_models";
import { makeOpenAIImageEditsRequest } from "../llm_calls/image_edits";
import { makeOpenAIImageGenerationRequest } from "../llm_calls/image_generation";
import { makeOpenAIResponsesRequest } from "../llm_calls/responses_api";
import A2AMetrics from "./A2AMetrics";
import AdditionalModelSettings from "./AdditionalModelSettings";
import AudioRenderer from "./AudioRenderer";
import { OPEN_AI_VOICE_SELECT_OPTIONS, OpenAIVoice } from "./chatConstants";
import ChatImageRenderer from "./ChatImageRenderer";
import ChatImageUpload from "./ChatImageUpload";
import { createChatDisplayMessage, createChatMultimodalMessage } from "./ChatImageUtils";
import CodeInterpreterOutput from "./CodeInterpreterOutput";
import CodeInterpreterTool from "./CodeInterpreterTool";
import { generateCodeSnippet } from "./CodeSnippets";
import EndpointSelector from "./EndpointSelector";
import { makeAnthropicMessagesRequest } from "../llm_calls/anthropic_messages";
import { makeOpenAIAudioSpeechRequest } from "../llm_calls/audio_speech";
import { makeOpenAIAudioTranscriptionRequest } from "../llm_calls/audio_transcriptions";
import { makeOpenAIChatCompletionRequest } from "../llm_calls/chat_completion";
import { makeOpenAIEmbeddingsRequest } from "../llm_calls/embeddings_api";
import { listMCPTools, fetchMCPServers } from "../../networking";
import { MCPServer } from "../../mcp_tools/types";
import { fetchAvailableModels, ModelGroup } from "../llm_calls/fetch_models";
import { makeOpenAIImageEditsRequest } from "../llm_calls/image_edits";
import { makeOpenAIImageGenerationRequest } from "../llm_calls/image_generation";
import { makeOpenAIResponsesRequest } from "../llm_calls/responses_api";
import CodeInterpreterOutput from "./CodeInterpreterOutput";
import { useCodeInterpreter } from "./useCodeInterpreter";
import { Agent, fetchAvailableAgents } from "../llm_calls/fetch_agents";
import { makeA2AStreamMessageRequest, makeA2ASendMessageRequest } from "../llm_calls/a2a_send_message";
import A2AMetrics from "./A2AMetrics";
import { A2ATaskMetadata } from "./types";
import MCPEventsDisplay, { MCPEvent } from "./MCPEventsDisplay";
import { EndpointType, getEndpointType } from "./mode_endpoint_mapping";
import ReasoningContent from "./ReasoningContent";
@ -66,8 +65,8 @@ import ResponsesImageUpload from "./ResponsesImageUpload";
import { createDisplayMessage, createMultimodalMessage } from "./ResponsesImageUtils";
import { SearchResultsDisplay } from "./SearchResultsDisplay";
import SessionManagement from "./SessionManagement";
import { MessageType } from "./types";
import CodeInterpreterTool from "./CodeInterpreterTool";
import { A2ATaskMetadata, MessageType } from "./types";
import { useCodeInterpreter } from "./useCodeInterpreter";
const { TextArea } = Input;
const { Dragger } = Upload;

View file

@ -1,8 +1,8 @@
import { waitFor } from "@testing-library/react";
import { renderWithProviders } from "../../../tests/test-utils";
import { fireEvent, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { KeyEditView } from "./key_edit_view";
import { renderWithProviders } from "../../../tests/test-utils";
import { KeyResponse } from "../key_team_helpers/key_list";
import { KeyEditView } from "./key_edit_view";
// Mock window.matchMedia
Object.defineProperty(window, "matchMedia", {
@ -143,4 +143,28 @@ describe("KeyEditView", () => {
expect(metadataTextarea).toHaveValue("{}");
});
});
it("should call onCancel when cancel button is clicked", async () => {
const onCancelMock = vi.fn();
const { getByText } = renderWithProviders(
<KeyEditView
keyData={MOCK_KEY_DATA}
onCancel={onCancelMock}
onSubmit={async () => {}}
accessToken={""}
userID={""}
userRole={""}
premiumUser={false}
/>,
);
await waitFor(() => {
expect(getByText("Cancel")).toBeInTheDocument();
});
const cancelButton = getByText("Cancel");
fireEvent.click(cancelButton);
expect(onCancelMock).toHaveBeenCalledTimes(1);
});
});

View file

@ -1,7 +1,9 @@
import GuardrailSelector from "@/components/guardrails/GuardrailSelector";
import { InfoCircleOutlined } from "@ant-design/icons";
import { TextInput, Button as TremorButton } from "@tremor/react";
import { Form, Input, Select, Switch, Tooltip } from "antd";
import { useEffect, useState } from "react";
import AgentSelector from "../agent_management/AgentSelector";
import { mapInternalToDisplayNames } from "../callback_info_helpers";
import KeyLifecycleSettings from "../common_components/KeyLifecycleSettings";
import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector";
@ -10,15 +12,13 @@ import { extractLoggingSettings, formatMetadataForDisplay, stripTagsFromMetadata
import { KeyResponse } from "../key_team_helpers/key_list";
import MCPServerSelector from "../mcp_server_management/MCPServerSelector";
import MCPToolPermissions from "../mcp_server_management/MCPToolPermissions";
import AgentSelector from "../agent_management/AgentSelector";
import NotificationsManager from "../molecules/notifications_manager";
import { fetchMCPAccessGroups, getPromptsList, modelAvailableCall, tagListCall } from "../networking";
import { getPromptsList, modelAvailableCall, tagListCall } from "../networking";
import { fetchTeamModels } from "../organisms/create_key_button";
import NumericalInput from "../shared/numerical_input";
import { Tag } from "../tag_management/types";
import EditLoggingSettings from "../team/EditLoggingSettings";
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
import { InfoCircleOutlined } from "@ant-design/icons";
interface KeyEditViewProps {
keyData: KeyResponse;
@ -82,13 +82,10 @@ export function KeyEditView({
premiumUser = false,
}: KeyEditViewProps) {
const [form] = Form.useForm();
const [userModels, setUserModels] = useState<string[]>([]);
const [promptsList, setPromptsList] = useState<string[]>([]);
const [tagsList, setTagsList] = useState<Record<string, Tag>>({});
const team = teams?.find((team) => team.team_id === keyData.team_id);
const [availableModels, setAvailableModels] = useState<string[]>([]);
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
const [disabledCallbacks, setDisabledCallbacks] = useState<string[]>(
Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
@ -98,18 +95,6 @@ export function KeyEditView({
const [rotationInterval, setRotationInterval] = useState<string>(keyData.rotation_interval || "");
const [isKeySaving, setIsKeySaving] = useState(false);
const fetchMcpAccessGroups = async () => {
if (!accessToken) return;
if (mcpAccessGroupsLoaded) return;
try {
const groups = await fetchMCPAccessGroups(accessToken);
setMcpAccessGroups(groups);
setMcpAccessGroupsLoaded(true);
} catch (error) {
console.error("Failed to fetch MCP access groups:", error);
}
};
useEffect(() => {
const fetchModels = async () => {
if (!userID || !userRole || !accessToken) return;
@ -526,7 +511,16 @@ export function KeyEditView({
</Form.Item>
<Form.Item label="Team ID" name="team_id">
<Select placeholder="Select team" style={{ width: "100%" }}>
<Select
placeholder="Select team"
showSearch
style={{ width: "100%" }}
filterOption={(input, option) => {
const team = teams?.find((t) => t.team_id === option?.value);
if (!team) return false;
return team.team_alias?.toLowerCase().includes(input.toLowerCase()) ?? false;
}}
>
{/* Only show All Team Models if team has models */}
{teams?.map((team) => (
<Select.Option key={team.team_id} value={team.team_id}>