mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Merge pull request #18310 from BerriAI/litellm_ui_button_styling
[Fix] Minor Styling Changes
This commit is contained in:
commit
aacde6a4a0
6 changed files with 108 additions and 72 deletions
21
AGENTS.md
21
AGENTS.md
|
|
@ -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**:
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue