Teams table empty state (#16738)

This commit is contained in:
yuneng-jiang 2025-11-17 19:49:33 -08:00 • committed by GitHub
parent bdb1e16dcf
commit 11f009a5a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 280 additions and 201 deletions

View file

@ -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", () => {

View file

@ -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 &&