feat(ui): link the Organization and Deleted By cells on Deleted Teams (#40751)

* feat(ui): link the Organization and Deleted By cells on Deleted Teams

Both columns rendered as plain text, so tracing a deleted team back to its
org or to whoever removed it meant copying an id into another page's search
box. Route them through IdentityCell with orgDetailHref and userDetailHref.
Team ID stays unlinked because the team itself is gone.

Claude-Session: https://claude.ai/code/session_01NfwfQhamRNnSqgXMUjf3h4

* test(ui): mount a router mock for the Deleted Teams 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:
ryan-crabbe-berri 2026-09-11 17:49:07 -07:00 committed by GitHub
parent c53f72c764
commit b27d2cce77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 11 deletions

View file

@ -5,6 +5,8 @@ import { renderWithProviders } from "../../../tests/test-utils";
import DeletedTeamsPage from "./DeletedTeamsPage";
import { useDeletedTeams, DeletedTeam } from "@/app/(dashboard)/hooks/teams/useTeams";
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({
useDeletedTeams: vi.fn(),
}));

View file

@ -4,6 +4,8 @@ import { renderWithProviders } from "../../../../tests/test-utils";
import { DeletedTeamsTable } from "./DeletedTeamsTable";
import { DeletedTeam } from "@/app/(dashboard)/hooks/teams/useTeams";
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
const makeDeletedTeam = (overrides: Partial<DeletedTeam> = {}): DeletedTeam => ({
team_id: "team-1",
team_alias: "Test Team",
@ -81,3 +83,21 @@ it("renders the shared pagination footer with the server row count", () => {
expect(screen.getByTestId("pagination-prev")).toBeEnabled();
expect(screen.getByTestId("pagination-next")).toBeDisabled();
});
it("links the organization and deleted by cells, leaving the deleted team id unlinked", () => {
renderWithProviders(
<DeletedTeamsTable teams={[makeDeletedTeam()]} isLoading={false} rowCount={1} {...paginationProps} />,
);
expect(screen.getByRole("link", { name: "org-1" })).toHaveAttribute("href", "/ui/organizations?org=org-1");
expect(screen.getByRole("link", { name: "user-1" })).toHaveAttribute("href", "/ui/users?user=user-1");
expect(screen.queryByRole("link", { name: "team-1" })).not.toBeInTheDocument();
});
it("leaves the default_user_id placeholder unlinked in the deleted by cell", () => {
const team = makeDeletedTeam({ deleted_by: "default_user_id", organization_id: null });
renderWithProviders(<DeletedTeamsTable teams={[team]} isLoading={false} rowCount={1} {...paginationProps} />);
expect(screen.getByText("default_user_id")).toBeInTheDocument();
expect(screen.queryByRole("link", { name: "default_user_id" })).not.toBeInTheDocument();
});

View file

@ -3,8 +3,20 @@
import { ColumnDef } from "@tanstack/react-table";
import { DataTableSortHeader } from "@/components/shared/DataTable";
import { DateCell, IdCell, ModelsCell, MoneyCell } from "@/components/shared/table_cells";
import { DateCell, IdCell, IdentityCell, ModelsCell, MoneyCell } from "@/components/shared/table_cells";
import { DeletedTeam } from "@/app/(dashboard)/hooks/teams/useTeams";
import { orgDetailHref, userDetailHref } from "@/utils/entityLinks";
function EntityCell({ value, href }: { value: string | null | undefined; href: string | undefined }) {
if (!value) {
return <span className="text-muted-foreground">-</span>;
}
return (
<span className="block max-w-60" title={value}>
<IdentityCell title={value} titleClassName="font-normal" href={href} />
</span>
);
}
export const getDeletedTeamsTableColumns = (): ColumnDef<DeletedTeam>[] => [
{
@ -78,7 +90,10 @@ export const getDeletedTeamsTableColumns = (): ColumnDef<DeletedTeam>[] => [
header: "Organization",
size: 150,
enableSorting: false,
cell: ({ row }) => <IdCell value={row.original.organization_id} variant="plain" />,
cell: ({ row }) => {
const orgId = row.original.organization_id;
return <EntityCell value={orgId} href={orgId ? orgDetailHref(orgId) : undefined} />;
},
},
{
id: "deleted_at",
@ -97,15 +112,8 @@ export const getDeletedTeamsTableColumns = (): ColumnDef<DeletedTeam>[] => [
size: 120,
enableSorting: false,
cell: ({ row }) => {
const value = row.original.deleted_by;
if (!value) {
return <span className="text-muted-foreground">-</span>;
}
return (
<span className="block max-w-60 truncate" title={value}>
{value}
</span>
);
const deletedBy = row.original.deleted_by;
return <EntityCell value={deletedBy} href={deletedBy ? userDetailHref(deletedBy) : undefined} />;
},
},
];