Merge pull request #18310 from BerriAI/litellm_ui_button_styling

[Fix] Minor Styling Changes
This commit is contained in:
yuneng-jiang 2025-12-22 09:42:27 -08:00 committed by GitHub
commit aacde6a4a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 108 additions and 72 deletions

View file

@ -49,6 +49,27 @@ LiteLLM is a unified interface for 100+ LLMs that:
- Test provider-specific functionality thoroughly
- Consider adding load tests for performance-critical changes
### MAKING CODE CHANGES FOR THE UI (IGNORE FOR BACKEND)
1. **Use Common Components as much as possible**:
- These are usually defined in the `common_components` directory
- Use these components as much as possible and avoid building new components unless needed
- Tremor components are deprecated; prefer using Ant Design (AntD) as much as possible
2. **Testing**:
- The codebase uses **Vitest** and **React Testing Library**
- **Query Priority Order**: Use query methods in this order: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByTestId`
- **Always use `screen`** instead of destructuring from `render()` (e.g., use `screen.getByText()` not `getByText`)
- **Wrap user interactions in `act()`**: Always wrap `fireEvent` calls with `act()` to ensure React state updates are properly handled
- **Use `query` methods for absence checks**: Use `queryBy*` methods (not `getBy*`) when expecting an element to NOT be present
- **Test names must start with "should"**: All test names should follow the pattern `it("should ...")`
- **Mock external dependencies**: Check `setupTests.ts` for global mocks and mock child components/networking calls as needed
- **Structure tests properly**:
- First test should verify the component renders successfully
- Subsequent tests should focus on functionality and user interactions
- Use `waitFor` for async operations that aren't already awaited
- **Avoid using `querySelector`**: Prefer React Testing Library queries over direct DOM manipulation
### IMPORTANT PATTERNS
1. **Function/Tool Calling**:

View file

@ -1,23 +1,23 @@
import React, { useState, useEffect } from "react";
import {
Card,
BarChart,
Subtitle,
Grid,
Card,
Col,
DateRangePickerValue,
Grid,
Icon,
MultiSelect,
MultiSelectItem,
TabPanel,
TabPanels,
Subtitle,
Tab,
TabGroup,
TabList,
Tab,
Icon,
TabPanel,
TabPanels,
Text,
} from "@tremor/react";
import UsageDatePicker from "./shared/usage_date_picker";
import React, { useEffect, useState } from "react";
import NotificationsManager from "./molecules/notifications_manager";
import UsageDatePicker from "./shared/usage_date_picker";
import { RefreshIcon } from "@heroicons/react/outline";
import { adminGlobalCacheActivity, cachingHealthCheckCall } from "./networking";
@ -271,9 +271,7 @@ const CacheDashboard: React.FC<CachePageProps> = ({ accessToken, token, userRole
<TabList className="flex justify-between mt-2 w-full items-center">
<div className="flex">
<Tab>Cache Analytics</Tab>
<Tab>
<pre>Cache Health</pre>
</Tab>
<Tab>Cache Health</Tab>
<Tab>Cache Settings</Tab>
</div>

View file

@ -1,5 +1,5 @@
import React from "react";
import { render, waitFor } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import { vi, test, expect } from "vitest";
import OrganizationInfoView from "./organization_view";
@ -82,3 +82,24 @@ test("renders organization view after loading data", async () => {
expect(findAllByText("Acme Corp")).toBeTruthy();
});
});
test("should display empty state when organization has no members", async () => {
const { organizationInfoCall } = await import("../networking");
(organizationInfoCall as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(mockOrg);
render(
<OrganizationInfoView
organizationId="org_123"
onClose={() => {}}
accessToken="test-token"
is_org_admin={false}
is_proxy_admin={false}
userModels={[]}
editOrg={false}
/>,
);
await waitFor(() => {
expect(screen.getByText("No members found")).toBeInTheDocument();
});
});

View file

@ -324,7 +324,6 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
</Grid>
</TabPanel>
{/* Budget Panel */}
<TabPanel>
<div className="space-y-4">
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[75vh]">
@ -340,47 +339,55 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
</TableHead>
<TableBody>
{orgData.members?.map((member, index) => (
<TableRow key={index}>
<TableCell>
<Text className="font-mono">{member.user_id}</Text>
</TableCell>
<TableCell>
<Text className="font-mono">{member.user_role}</Text>
</TableCell>
<TableCell>
<Text>${formatNumberWithCommas(member.spend, 4)}</Text>
</TableCell>
<TableCell>
<Text>{new Date(member.created_at).toLocaleString()}</Text>
</TableCell>
<TableCell>
{canEditOrg && (
<>
<Icon
icon={PencilAltIcon}
size="sm"
onClick={() => {
setSelectedEditMember({
role: member.user_role,
user_email: member.user_email,
user_id: member.user_id,
});
setIsEditMemberModalVisible(true);
}}
/>
<Icon
icon={TrashIcon}
size="sm"
onClick={() => {
handleMemberDelete(member);
}}
/>
</>
)}
{orgData.members && orgData.members.length > 0 ? (
orgData.members.map((member, index) => (
<TableRow key={index}>
<TableCell>
<Text className="font-mono">{member.user_id}</Text>
</TableCell>
<TableCell>
<Text className="font-mono">{member.user_role}</Text>
</TableCell>
<TableCell>
<Text>${formatNumberWithCommas(member.spend, 4)}</Text>
</TableCell>
<TableCell>
<Text>{new Date(member.created_at).toLocaleString()}</Text>
</TableCell>
<TableCell>
{canEditOrg && (
<>
<Icon
icon={PencilAltIcon}
size="sm"
onClick={() => {
setSelectedEditMember({
role: member.user_role,
user_email: member.user_email,
user_id: member.user_id,
});
setIsEditMemberModalVisible(true);
}}
/>
<Icon
icon={TrashIcon}
size="sm"
onClick={() => {
handleMemberDelete(member);
}}
/>
</>
)}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={5} className="text-center py-8">
<Text className="text-gray-500">No members found</Text>
</TableCell>
</TableRow>
))}
)}
</TableBody>
</Table>
</Card>

View file

@ -1,21 +1,10 @@
import React, { useState, useEffect } from "react";
import {
Card,
Title,
Text,
Button as TremorButton,
Table,
TableHead,
TableHeaderCell,
TableBody,
TableRow,
TableCell,
} from "@tremor/react";
import { Button, Checkbox, Empty } from "antd";
import { ReloadOutlined, SaveOutlined } from "@ant-design/icons";
import { getTeamPermissionsCall, teamPermissionsUpdateCall } from "@/components/networking";
import { getPermissionInfo } from "./permission_definitions";
import { ReloadOutlined, SaveOutlined } from "@ant-design/icons";
import { Card, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Text, Title } from "@tremor/react";
import { Button, Checkbox, Empty } from "antd";
import React, { useEffect, useState } from "react";
import NotificationsManager from "../molecules/notifications_manager";
import { getPermissionInfo } from "./permission_definitions";
interface MemberPermissionsProps {
teamId: string;
@ -94,9 +83,9 @@ const MemberPermissions: React.FC<MemberPermissionsProps> = ({ teamId, accessTok
<Button icon={<ReloadOutlined />} onClick={handleReset}>
Reset
</Button>
<TremorButton onClick={handleSave} loading={saving} className="flex items-center gap-2">
<Button onClick={handleSave} loading={saving} type="primary">
<SaveOutlined /> Save Changes
</TremorButton>
</Button>
</div>
)}
</div>

View file

@ -508,7 +508,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
Back to Teams
</TremorButton>
<Title>{info.team_alias}</Title>
<div className="flex items-center cursor-pointer">
<div className="flex items-center">
<Text className="text-gray-500 font-mono">{info.team_id}</Text>
<Button
type="text"