mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
feat(ui): link the User ID, Created By and Deleted By cells on Deleted Keys (#40750)
* feat(ui): link the User ID, Created By and Deleted By cells on Deleted Keys All three columns rendered as plain text, so auditing a deleted key meant copying an id into the Users page search box. Route them through IdentityCell with userDetailHref, which keeps the proxy admin placeholder unlinked. User Email and Team Alias stay as they are: the deleted key table has no column for either, so the API never populates them. Claude-Session: https://claude.ai/code/session_01NfwfQhamRNnSqgXMUjf3h4 * test(ui): mount a router mock for the Deleted Keys page test The page test renders the table, and the newly linked cells call useRouter, which throws without an App Router mounted. Matches how the other 35 test files in the suite stub next/navigation. Claude-Session: https://claude.ai/code/session_01NfwfQhamRNnSqgXMUjf3h4
This commit is contained in:
parent
1be930664f
commit
06964e5603
3 changed files with 36 additions and 4 deletions
|
|
@ -5,6 +5,8 @@ import { renderWithProviders } from "../../../tests/test-utils";
|
|||
import DeletedKeysPage from "./DeletedKeysPage";
|
||||
import { useDeletedKeys, DeletedKeyResponse } from "@/app/(dashboard)/hooks/keys/useKeys";
|
||||
|
||||
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/keys/useKeys", () => ({
|
||||
useDeletedKeys: vi.fn(),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { renderWithProviders } from "../../../../tests/test-utils";
|
|||
import { DeletedKeysTable } from "./DeletedKeysTable";
|
||||
import { DeletedKeyResponse } from "@/app/(dashboard)/hooks/keys/useKeys";
|
||||
|
||||
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
|
||||
|
||||
const makeDeletedKey = (overrides: Partial<DeletedKeyResponse> = {}): DeletedKeyResponse =>
|
||||
({
|
||||
token: "sk-1234567890abcdef",
|
||||
|
|
@ -86,3 +88,19 @@ it("should show the empty state when there are no deleted keys", () => {
|
|||
|
||||
expect(screen.getByText("No deleted keys found")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("links the owner, creator and deleter cells to their user detail pages", () => {
|
||||
renderWithProviders(<DeletedKeysTable {...defaultProps} keys={[makeDeletedKey({ deleted_by: "deleter-1" })]} />);
|
||||
|
||||
expect(screen.getByRole("link", { name: "user-1" })).toHaveAttribute("href", "/ui/users?user=user-1");
|
||||
expect(screen.getByRole("link", { name: "creator-1" })).toHaveAttribute("href", "/ui/users?user=creator-1");
|
||||
expect(screen.getByRole("link", { name: "deleter-1" })).toHaveAttribute("href", "/ui/users?user=deleter-1");
|
||||
});
|
||||
|
||||
it("leaves the default_user_id placeholder unlinked", () => {
|
||||
const placeholderKey = makeDeletedKey({ user_id: "default_user_id", created_by: "default_user_id" });
|
||||
renderWithProviders(<DeletedKeysTable {...defaultProps} keys={[placeholderKey]} />);
|
||||
|
||||
expect(screen.getAllByText("default_user_id")).toHaveLength(2);
|
||||
expect(screen.queryByRole("link", { name: "default_user_id" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import { DataTableSortHeader } from "@/components/shared/DataTable";
|
||||
import { DateCell, IdCell, MoneyCell } from "@/components/shared/table_cells";
|
||||
import { DateCell, IdCell, IdentityCell, MoneyCell } from "@/components/shared/table_cells";
|
||||
import { DeletedKeyResponse } from "@/app/(dashboard)/hooks/keys/useKeys";
|
||||
import { userDetailHref } from "@/utils/entityLinks";
|
||||
|
||||
function TruncatedTextCell({ value }: { value: string | null | undefined }) {
|
||||
if (!value) {
|
||||
|
|
@ -17,6 +18,17 @@ function TruncatedTextCell({ value }: { value: string | null | undefined }) {
|
|||
);
|
||||
}
|
||||
|
||||
function UserLinkCell({ userId }: { userId: string | null | undefined }) {
|
||||
if (!userId) {
|
||||
return <span className="text-muted-foreground">-</span>;
|
||||
}
|
||||
return (
|
||||
<span className="block max-w-60" title={userId}>
|
||||
<IdentityCell title={userId} titleClassName="font-normal" href={userDetailHref(userId)} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export const getDeletedKeysTableColumns = (): ColumnDef<DeletedKeyResponse>[] => [
|
||||
{
|
||||
id: "token",
|
||||
|
|
@ -89,7 +101,7 @@ export const getDeletedKeysTableColumns = (): ColumnDef<DeletedKeyResponse>[] =>
|
|||
header: "User ID",
|
||||
size: 120,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => <IdCell value={row.original.user_id} variant="plain" />,
|
||||
cell: ({ row }) => <UserLinkCell userId={row.original.user_id} />,
|
||||
},
|
||||
{
|
||||
id: "created_at",
|
||||
|
|
@ -107,7 +119,7 @@ export const getDeletedKeysTableColumns = (): ColumnDef<DeletedKeyResponse>[] =>
|
|||
header: "Created By",
|
||||
size: 120,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => <TruncatedTextCell value={row.original.created_by} />,
|
||||
cell: ({ row }) => <UserLinkCell userId={row.original.created_by} />,
|
||||
},
|
||||
{
|
||||
id: "deleted_at",
|
||||
|
|
@ -125,6 +137,6 @@ export const getDeletedKeysTableColumns = (): ColumnDef<DeletedKeyResponse>[] =>
|
|||
header: "Deleted By",
|
||||
size: 120,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => <TruncatedTextCell value={row.original.deleted_by} />,
|
||||
cell: ({ row }) => <UserLinkCell userId={row.original.deleted_by} />,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue