fix(ui): remount the per-model budget editor when the create user form resets

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
jesus 2026-09-18 21:50:11 +00:00
parent 7d2011fc01
commit 4e90a80275
2 changed files with 75 additions and 3 deletions

View file

@ -833,5 +833,70 @@ describe("CreateUserButton", () => {
});
expect(mockUserCreateCall.mock.calls[0][2]).not.toHaveProperty("model_max_budget");
});
it("clears the per-model budget rows after a user is created in standalone mode", async () => {
const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never });
vi.mocked(networking.modelAvailableCall).mockResolvedValue({ data: [{ id: "gpt-4" }] } as any);
mockUserCreateCall.mockResolvedValue({ data: { user_id: "budget-user" } });
mockInvitationCreateCall.mockResolvedValue({
id: "inv-budget",
user_id: "budget-user",
has_user_setup_sso: false,
} as any);
renderWithProviders(
<CreateUserButton
{...defaultProps}
premiumUser
possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }}
/>,
);
const dialog = await openStandaloneModal(user);
await user.type(within(dialog).getByLabelText(/user email/i), "budget@example.com");
await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i }));
await user.click(screen.getByText("User"));
await user.click(within(dialog).getByRole("button", { name: /Add Model Budget/i }));
await chooseSelectOption(user, within(dialog).getByPlaceholderText("Select model"), "gpt-4");
fireEvent.change(within(dialog).getByPlaceholderText("Max spend ($)"), { target: { value: "3" } });
await user.click(within(dialog).getByRole("button", { name: /invite user/i }));
await waitFor(() => {
expect(mockUserCreateCall).toHaveBeenCalled();
});
expect(within(dialog).queryByPlaceholderText("Max spend ($)")).not.toBeInTheDocument();
expect(within(dialog).getByRole("button", { name: /Add Model Budget/i, hidden: true })).toBeInTheDocument();
});
it("clears the per-model budget rows after a user is created in embedded mode", async () => {
const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never });
const onUserCreated = vi.fn();
vi.mocked(networking.modelAvailableCall).mockResolvedValue({ data: [{ id: "gpt-4" }] } as any);
mockUserCreateCall.mockResolvedValue({ data: { user_id: "embedded-budget-user" } });
renderWithProviders(
<CreateUserButton
{...defaultProps}
premiumUser
onUserCreated={onUserCreated}
possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }}
isEmbedded
/>,
);
await user.type(screen.getByLabelText(/user email/i), "embedded-budget@example.com");
await user.click(screen.getByRole("combobox", { name: /user role/i }));
await user.click(screen.getByText("User"));
await user.click(screen.getByRole("button", { name: /Add Model Budget/i }));
await chooseSelectOption(user, screen.getByPlaceholderText("Select model"), "gpt-4");
fireEvent.change(screen.getByPlaceholderText("Max spend ($)"), { target: { value: "3" } });
await user.click(screen.getByRole("button", { name: /create user/i }));
await waitFor(() => {
expect(mockUserCreateCall).toHaveBeenCalled();
});
expect(screen.queryByPlaceholderText("Max spend ($)")).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: /Add Model Budget/i })).toBeInTheDocument();
});
});
});

View file

@ -166,6 +166,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
const [apiuser, setApiuser] = useState<boolean>(false);
const [userModels, setUserModels] = useState<string[]>([]);
const [modelMaxBudget, setModelMaxBudget] = useState<ModelMaxBudget>({});
const [modelMaxBudgetKey, setModelMaxBudgetKey] = useState(0);
const [isPersonalKeyOpen, setIsPersonalKeyOpen] = useState(false);
const [isInvitationLinkModalVisible, setIsInvitationLinkModalVisible] = useState(false);
const [invitationLinkData, setInvitationLinkData] = useState<InvitationLink | null>(null);
@ -198,11 +199,16 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
fetchData();
}, []);
const resetModelMaxBudget = () => {
setModelMaxBudget({});
setModelMaxBudgetKey((prev) => prev + 1);
};
const handleCancel = () => {
setIsModalVisible(false);
setApiuser(false);
form.reset(defaultValues);
setModelMaxBudget({});
resetModelMaxBudget();
};
const handleCreate = async (formValues: CreateUserFormValues) => {
@ -223,7 +229,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
if (onUserCreated && isEmbedded) {
onUserCreated(user_id);
form.reset(defaultValues);
setModelMaxBudget({});
resetModelMaxBudget();
return;
}
@ -240,7 +246,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
toast.success("API user Created");
form.reset(defaultValues);
setModelMaxBudget({});
resetModelMaxBudget();
localStorage.removeItem("userData" + userID);
} catch (error: any) {
const errorMessage = error.response?.data?.detail || error?.message || "Error creating the user";
@ -290,6 +296,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
const modelMaxBudgetField = (
<ModelMaxBudgetField
key={`model-max-budget-${modelMaxBudgetKey}`}
premiumUser={premiumUser}
value={modelMaxBudget}
onChange={setModelMaxBudget}