mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
Teams table empty state (#16738)
This commit is contained in:
parent
bdb1e16dcf
commit
11f009a5a4
2 changed files with 280 additions and 201 deletions
|
|
@ -270,6 +270,78 @@ describe("OldTeams - handleCreate organization handling", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("OldTeams - empty state", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should display empty state message when teams array is empty", () => {
|
||||
const { getByText } = render(
|
||||
<OldTeams
|
||||
teams={[]}
|
||||
searchParams={{}}
|
||||
accessToken="test-token"
|
||||
setTeams={vi.fn()}
|
||||
userID="user-123"
|
||||
userRole="Admin"
|
||||
organizations={[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText("No teams found")).toBeInTheDocument();
|
||||
expect(getByText("Adjust your filters or create a new team")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display empty state message when teams is null", () => {
|
||||
const { getByText } = render(
|
||||
<OldTeams
|
||||
teams={null}
|
||||
searchParams={{}}
|
||||
accessToken="test-token"
|
||||
setTeams={vi.fn()}
|
||||
userID="user-123"
|
||||
userRole="Admin"
|
||||
organizations={[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText("No teams found")).toBeInTheDocument();
|
||||
expect(getByText("Adjust your filters or create a new team")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not display empty state when teams array has items", () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<OldTeams
|
||||
teams={[
|
||||
{
|
||||
team_id: "1",
|
||||
team_alias: "Test Team",
|
||||
organization_id: "org-123",
|
||||
models: ["gpt-4"],
|
||||
max_budget: 100,
|
||||
budget_duration: "1d",
|
||||
tpm_limit: 1000,
|
||||
rpm_limit: 1000,
|
||||
created_at: new Date().toISOString(),
|
||||
keys: [],
|
||||
members_with_roles: [],
|
||||
},
|
||||
]}
|
||||
searchParams={{}}
|
||||
accessToken="test-token"
|
||||
setTeams={vi.fn()}
|
||||
userID="user-123"
|
||||
userRole="Admin"
|
||||
organizations={[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByText("No teams found")).not.toBeInTheDocument();
|
||||
expect(queryByText("Adjust your filters or create a new team")).not.toBeInTheDocument();
|
||||
expect(getByText("Test Team")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("OldTeams - helper functions", () => {
|
||||
describe("getAdminOrganizations", () => {
|
||||
it("should return all organizations for Admin role", () => {
|
||||
|
|
|
|||
|
|
@ -760,212 +760,219 @@ const Teams: React.FC<TeamProps> = ({
|
|||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{teams && teams.length > 0
|
||||
? teams
|
||||
.filter((team) => {
|
||||
if (!currentOrg) return true;
|
||||
return team.organization_id === currentOrg.organization_id;
|
||||
})
|
||||
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
||||
.map((team: any) => (
|
||||
<TableRow key={team.team_id}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["team_alias"]}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={team.team_id}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => {
|
||||
// Add click handler
|
||||
setSelectedTeamId(team.team_id);
|
||||
}}
|
||||
>
|
||||
{team.team_id.slice(0, 7)}...
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{formatNumberWithCommas(team["spend"], 4)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["max_budget"] !== null && team["max_budget"] !== undefined
|
||||
? team["max_budget"]
|
||||
: "No limit"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={team.models.length > 3 ? "px-0" : ""}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{Array.isArray(team.models) ? (
|
||||
<div className="flex flex-col">
|
||||
{team.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start">
|
||||
{team.models.length > 3 && (
|
||||
<div>
|
||||
<Icon
|
||||
icon={
|
||||
expandedAccordions[team.team_id]
|
||||
? ChevronDownIcon
|
||||
: ChevronRightIcon
|
||||
}
|
||||
className="cursor-pointer"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setExpandedAccordions((prev) => ({
|
||||
...prev,
|
||||
[team.team_id]: !prev[team.team_id],
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(0, 3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size={"xs"} color="blue">
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
{team.models.length > 3 && !expandedAccordions[team.team_id] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
{teams && teams.length > 0 ? (
|
||||
teams
|
||||
.filter((team) => {
|
||||
if (!currentOrg) return true;
|
||||
return team.organization_id === currentOrg.organization_id;
|
||||
})
|
||||
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
||||
.map((team: any) => (
|
||||
<TableRow key={team.team_id}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["team_alias"]}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={team.team_id}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => {
|
||||
// Add click handler
|
||||
setSelectedTeamId(team.team_id);
|
||||
}}
|
||||
>
|
||||
{team.team_id.slice(0, 7)}...
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{formatNumberWithCommas(team["spend"], 4)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["max_budget"] !== null && team["max_budget"] !== undefined
|
||||
? team["max_budget"]
|
||||
: "No limit"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={team.models.length > 3 ? "px-0" : ""}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{Array.isArray(team.models) ? (
|
||||
<div className="flex flex-col">
|
||||
{team.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start">
|
||||
{team.models.length > 3 && (
|
||||
<div>
|
||||
<Icon
|
||||
icon={
|
||||
expandedAccordions[team.team_id]
|
||||
? ChevronDownIcon
|
||||
: ChevronRightIcon
|
||||
}
|
||||
className="cursor-pointer"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setExpandedAccordions((prev) => ({
|
||||
...prev,
|
||||
[team.team_id]: !prev[team.team_id],
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(0, 3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size={"xs"} color="blue">
|
||||
<Text>
|
||||
+{team.models.length - 3}{" "}
|
||||
{team.models.length - 3 === 1
|
||||
? "more model"
|
||||
: "more models"}
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[team.team_id] && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index + 3} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index + 3} size={"xs"} color="blue">
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
{team.models.length > 3 && !expandedAccordions[team.team_id] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>
|
||||
+{team.models.length - 3}{" "}
|
||||
{team.models.length - 3 === 1 ? "more model" : "more models"}
|
||||
</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[team.team_id] && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index + 3} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index + 3} size={"xs"} color="blue">
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{team.organization_id}</TableCell>
|
||||
<TableCell>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].keys &&
|
||||
perTeamInfo[team.team_id].keys.length}{" "}
|
||||
Keys
|
||||
</Text>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].team_info &&
|
||||
perTeamInfo[team.team_id].team_info.members_with_roles &&
|
||||
perTeamInfo[team.team_id].team_info.members_with_roles.length}{" "}
|
||||
Members
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{userRole == "Admin" ? (
|
||||
<>
|
||||
<Tooltip title="Edit team">
|
||||
{" "}
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
className="cursor-pointer hover:text-blue-600"
|
||||
onClick={() => {
|
||||
setSelectedTeamId(team.team_id);
|
||||
setEditTeam(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete team">
|
||||
{" "}
|
||||
<Icon
|
||||
onClick={() => handleDelete(team.team_id)}
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
className="cursor-pointer hover:text-red-600"
|
||||
data-testid="delete-team-button"
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{team.organization_id}</TableCell>
|
||||
<TableCell>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].keys &&
|
||||
perTeamInfo[team.team_id].keys.length}{" "}
|
||||
Keys
|
||||
</Text>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].team_info &&
|
||||
perTeamInfo[team.team_id].team_info.members_with_roles &&
|
||||
perTeamInfo[team.team_id].team_info.members_with_roles.length}{" "}
|
||||
Members
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{userRole == "Admin" ? (
|
||||
<>
|
||||
<Tooltip title="Edit team">
|
||||
{" "}
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
className="cursor-pointer hover:text-blue-600"
|
||||
onClick={() => {
|
||||
setSelectedTeamId(team.team_id);
|
||||
setEditTeam(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete team">
|
||||
{" "}
|
||||
<Icon
|
||||
onClick={() => handleDelete(team.team_id)}
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
className="cursor-pointer hover:text-red-600"
|
||||
data-testid="delete-team-button"
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={9} className="text-center">
|
||||
<div className="flex flex-col items-center justify-center py-4">
|
||||
<Text className="text-lg font-medium mb-2">No teams found</Text>
|
||||
<Text className="text-sm">Adjust your filters or create a new team</Text>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{isDeleteModalOpen &&
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue