diff --git a/src/features/organization/roles/constants/tableDefinition.test.tsx b/src/features/organization/roles/constants/tableDefinition.test.tsx
new file mode 100644
index 000000000..a57da6fd2
--- /dev/null
+++ b/src/features/organization/roles/constants/tableDefinition.test.tsx
@@ -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(
+ ,
+ );
+}
+
+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']);
+ });
+});
diff --git a/src/features/organization/roles/constants/tableDefinition.tsx b/src/features/organization/roles/constants/tableDefinition.tsx
index ecacdba0f..9bc2eb74f 100644
--- a/src/features/organization/roles/constants/tableDefinition.tsx
+++ b/src/features/organization/roles/constants/tableDefinition.tsx
@@ -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();
+function assignedUserCount(role: SchemaOrganizationRole) {
+ return role.userIds?.length ?? 0;
+}
export const dataTableColumns: Array> = [
{
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) => (
{props.row.original.id}
),
- }),
- 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',
+ },
];
diff --git a/src/features/organization/roles/index.tsx b/src/features/organization/roles/index.tsx
index 2ef60b8f7..c9ba93bb6 100644
--- a/src/features/organization/roles/index.tsx
+++ b/src/features/organization/roles/index.tsx
@@ -75,7 +75,13 @@ export function OrgConfigRolesIndex() {
}>
-
+