Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @vitest-environment jsdom
*/
import { SimpleBrowseDataTable } from '@/components/SimpleBrowseDataTable';
import { SchemaOrganizationRole } from '@/integrations/api/api.gen';
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it } from 'vitest';
import { dataTableColumns } from './tableDefinition';

afterEach(() => cleanup());

function role(id: string, roleName: string, userCount: number): SchemaOrganizationRole {
return {
id,
organizationId: 'org-1',
roleName,
userIds: Array.from({ length: userCount }, (_, index) => `user-${index}`),
};
}

function renderedRoleNames() {
return Array.from(document.querySelectorAll('tbody tr')).map((row) => row.querySelector('td')?.textContent);
}

function renderRoles(roles: SchemaOrganizationRole[]) {
render(
<SimpleBrowseDataTable columns={dataTableColumns} data={roles} filter={{ label: 'Filter roles' }} paginated />,
);
}

async function typeFilter(text: string) {
await act(async () => {
fireEvent.change(screen.getByRole('searchbox', { name: 'Filter roles' }), { target: { value: text } });
});
}

describe('org roles table columns', () => {
it('filters by role name, not by id or user count', async () => {
renderRoles([role('ab12', 'Admin', 1), role('cd34', 'Developer', 12), role('ef56', 'Viewer', 0)]);

await typeFilter('dev');
expect(renderedRoleNames()).toEqual(['Developer']);

await typeFilter('ab12');
expect(screen.getByText('No results.')).toBeTruthy();

await typeFilter('12');
expect(screen.getByText('No results.')).toBeTruthy();
});

it('sorts by how many users hold the role, showing a role nobody holds as unassigned', () => {
renderRoles([role('a', 'Admin', 2), role('b', 'Developer', 12), role('c', 'Viewer', 0)]);

fireEvent.click(screen.getByRole('button', { name: 'Users Assigned' }));

expect(renderedRoleNames()).toEqual(['Viewer', 'Admin', 'Developer']);
expect(screen.getByText('No users assigned')).toBeTruthy();
});

it('sorts by role name', () => {
renderRoles([role('a', 'viewer', 0), role('b', 'Admin', 0), role('c', 'developer', 0)]);

fireEvent.click(screen.getByRole('button', { name: 'Role Name' }));

expect(renderedRoleNames()).toEqual(['Admin', 'developer', 'viewer']);
});
});
32 changes: 16 additions & 16 deletions src/features/organization/roles/constants/tableDefinition.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
import { SchemaOrganizationRole } from '@/integrations/api/api.gen';
import { ColumnDef, createColumnHelper } from '@/lib/table';
import { ColumnDef } from '@/lib/table';

const columnHelper = createColumnHelper<SchemaOrganizationRole>();
function assignedUserCount(role: SchemaOrganizationRole) {
return role.userIds?.length ?? 0;
}

export const dataTableColumns: Array<ColumnDef<SchemaOrganizationRole>> = [
{
header: 'Role Name',
accessorKey: 'roleName',
enableSorting: false,
enableSorting: true,
},
columnHelper.display({
{
header: 'ID',
id: 'id',
enableSorting: false,
accessorKey: 'id',
enableSorting: true,
enableGlobalFilter: false,
// Role names are not unique; surfacing the id makes roles that share a name distinguishable.
cell: (props) => (
<span className="font-mono text-xs text-muted-foreground" title={props.row.original.id}>
{props.row.original.id}
</span>
),
}),
columnHelper.display({
},
{
header: 'Users Assigned',
enableSorting: false,
id: 'userIds',
cell: (props) => {
if (!props.row.original.userIds || props.row.original.userIds.length === 0) {
return 'No users assigned';
}
return props.row.original.userIds.length;
},
}),
accessorFn: assignedUserCount,
enableSorting: true,
enableGlobalFilter: false,
cell: (props) => assignedUserCount(props.row.original) || 'No users assigned',
},
];
8 changes: 7 additions & 1 deletion src/features/organization/roles/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ export function OrgConfigRolesIndex() {
<SubNavMenu />
<OrgPageLayout>
<Suspense fallback={<Loading className="flex flex-col items-center justify-center h-full" text="Loading..." />}>
<SimpleBrowseDataTable data={orgRoles} columns={dataTableColumns} onRowClick={onRowClick}>
<SimpleBrowseDataTable
data={orgRoles}
columns={dataTableColumns}
onRowClick={onRowClick}
filter={{ label: 'Filter roles', placeholder: 'Filter by role name' }}
paginated
>
<Button
variant="defaultOutline"
onClick={onRefreshClick}
Expand Down
Loading