mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
fix: stop a cleared Organization field from failing key creation (#39316)
* fix: stop a cleared Organization field from failing key creation Clearing the Organization combobox in the Create Key modal left organization_id set to an empty string, so /key/generate looked up an organization named "" and failed with "Organization doesn't exist in db. Organization=". OrganizationDropdown now emits null on clear, and GenerateKeyRequest normalizes an empty organization_id or project_id to None the same way it already does for team_id. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: drop customer-specific docstring from key request normalization test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: yassin <yassin@berri.ai>
This commit is contained in:
parent
e046aee3d5
commit
92122086ec
6 changed files with 32 additions and 9 deletions
|
|
@ -1216,7 +1216,7 @@ class GenerateKeyRequest(KeyRequestBase):
|
|||
organization_id: str | None = None
|
||||
project_id: str | None = None
|
||||
|
||||
@field_validator("team_id", "organization_id", mode="before")
|
||||
@field_validator("team_id", "organization_id", "project_id", mode="before")
|
||||
@classmethod
|
||||
def treat_cleared_id_as_unset(cls, v: object) -> object:
|
||||
if v == "":
|
||||
|
|
|
|||
|
|
@ -17505,6 +17505,18 @@ def test_generate_key_request_blank_team_id_is_personal():
|
|||
assert GenerateKeyRequest(team_id="team-1").team_id == "team-1"
|
||||
|
||||
|
||||
def test_generate_key_request_blank_organization_and_project_id_are_unset():
|
||||
from litellm.proxy._types import RegenerateKeyRequest
|
||||
|
||||
cleared = GenerateKeyRequest(organization_id="", project_id="")
|
||||
assert cleared.organization_id is None
|
||||
assert cleared.project_id is None
|
||||
assert "organization_id" not in cleared.model_dump(exclude_none=True)
|
||||
assert RegenerateKeyRequest(organization_id="").organization_id is None
|
||||
assert GenerateKeyRequest(organization_id="org-1", project_id="proj-1").organization_id == "org-1"
|
||||
assert GenerateKeyRequest(organization_id="org-1", project_id="proj-1").project_id == "proj-1"
|
||||
|
||||
|
||||
def test_key_request_blank_organization_id_is_unset():
|
||||
from litellm.proxy._types import RegenerateKeyRequest, UpdateKeyRequest
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,17 @@ describe("OrganizationDropdown", () => {
|
|||
expect(onChange.mock.calls[0][0]).toBe("org-1");
|
||||
});
|
||||
|
||||
it("emits null, never the empty string, when the selection is cleared", async () => {
|
||||
const onChange = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
render(<OrganizationDropdown organizations={MOCK_ORGS} value="org-1" onChange={onChange} />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Clear" }));
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it("should filter options by organization id", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<OrganizationDropdown organizations={MOCK_ORGS} />);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Organization } from "../networking";
|
|||
interface OrganizationDropdownProps {
|
||||
organizations?: Organization[] | null;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
onChange?: (value: string | null) => void;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
|
|
@ -32,7 +32,7 @@ const OrganizationDropdown: React.FC<OrganizationDropdownProps> = ({
|
|||
sublabel: org.organization_id,
|
||||
}))}
|
||||
value={value}
|
||||
onValueChange={(organizationId) => onChange?.(organizationId)}
|
||||
onValueChange={(organizationId) => onChange?.(organizationId || null)}
|
||||
placeholder={placeholder}
|
||||
emptyText={loading ? "Loading organizations…" : "No organizations found"}
|
||||
disabled={disabled}
|
||||
|
|
|
|||
|
|
@ -587,9 +587,9 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
|
|||
}
|
||||
};
|
||||
|
||||
const changeOrganization = (write: FieldWrite) => (orgId: string) => {
|
||||
write(orgId || undefined);
|
||||
setSelectedOrganizationId(orgId || null);
|
||||
const changeOrganization = (write: FieldWrite) => (orgId: string | null) => {
|
||||
write(orgId ?? undefined);
|
||||
setSelectedOrganizationId(orgId);
|
||||
// Clear team and project when org changes
|
||||
setSelectedCreateKeyTeam(null);
|
||||
setSelectedProjectId(null);
|
||||
|
|
|
|||
|
|
@ -303,9 +303,9 @@ export function KeyEditView({
|
|||
}
|
||||
};
|
||||
|
||||
const handleOrganizationChange = (setField: (value: string | null) => void, orgId: string | undefined) => {
|
||||
setField(orgId || null);
|
||||
setSelectedOrganizationId(orgId || null);
|
||||
const handleOrganizationChange = (setField: (value: string | null) => void, orgId: string | null) => {
|
||||
setField(orgId);
|
||||
setSelectedOrganizationId(orgId);
|
||||
form.setValue("team_id", undefined);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue