diff --git a/src/features/instance/config/users/constants/tableDefinition.test.tsx b/src/features/instance/config/users/constants/tableDefinition.test.tsx new file mode 100644 index 000000000..439018298 --- /dev/null +++ b/src/features/instance/config/users/constants/tableDefinition.test.tsx @@ -0,0 +1,79 @@ +/** + * @vitest-environment jsdom + */ +import { SimpleBrowseDataTable } from '@/components/SimpleBrowseDataTable'; +import { LocalUser } from '@/integrations/api/api.patch'; +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it } from 'vitest'; +import { dataTableColumns } from './tableDefinition'; + +afterEach(() => cleanup()); + +const CREATED = 1_700_000_000_000; + +function user(username: string, role: string, createdOffset = 0): LocalUser { + return { + username, + active: true, + role: { id: `role-${role}`, role, permission: {}, __createdtime__: CREATED, __updatedtime__: CREATED }, + __createdtime__: CREATED + createdOffset, + __updatedtime__: CREATED + createdOffset, + }; +} + +function renderedUsernames() { + return Array.from(document.querySelectorAll('tbody tr')).map((row) => row.querySelector('td')?.textContent); +} + +/** As the Users page renders it, including its initial username sort. */ +function renderUsers(users: LocalUser[]) { + render( + , + ); +} + +async function typeFilter(text: string) { + await act(async () => { + fireEvent.change(screen.getByRole('searchbox', { name: 'Filter users' }), { target: { value: text } }); + }); +} + +describe('instance users table columns', () => { + it('filters by username and role, but not by the active flag or the timestamps', async () => { + renderUsers([user('admin', 'super_user'), user('reader', 'read_only'), user('etl-bot', 'writer')]); + + await typeFilter('READ'); + expect(renderedUsernames()).toEqual(['reader']); + + await typeFilter('writer'); + expect(renderedUsernames()).toEqual(['etl-bot']); + + // Every user is active and shares one timestamp prefix, so either would match the whole list. + await typeFilter('true'); + expect(screen.getByText('No results.')).toBeTruthy(); + await typeFilter(String(CREATED).slice(0, 6)); + expect(screen.getByText('No results.')).toBeTruthy(); + }); + + it('sorts by role, and by when the user was created', () => { + renderUsers([ + user('carol', 'writer', 2_000), + user('alice', 'super_user', 3_000), + user('bob', 'read_only', 1_000), + ]); + + expect(renderedUsernames()).toEqual(['alice', 'bob', 'carol']); + + fireEvent.click(screen.getByRole('button', { name: 'Role' })); + expect(renderedUsernames()).toEqual(['bob', 'alice', 'carol']); + + fireEvent.click(screen.getByRole('button', { name: 'Created' })); + expect(renderedUsernames()).toEqual(['bob', 'carol', 'alice']); + }); +}); diff --git a/src/features/instance/config/users/constants/tableDefinition.ts b/src/features/instance/config/users/constants/tableDefinition.ts index b4c2cc3cc..e52174b5d 100644 --- a/src/features/instance/config/users/constants/tableDefinition.ts +++ b/src/features/instance/config/users/constants/tableDefinition.ts @@ -1,40 +1,40 @@ import { LocalUser } from '@/integrations/api/api.patch'; -import { ColumnDef, createColumnHelper } from '@/lib/table'; +import { ColumnDef } from '@/lib/table'; import { translateSecondsToAgo } from '@/lib/translateSecondsToAgo'; -const columnHelper = createColumnHelper(); - export const dataTableColumns: Array> = [ { header: 'Username', accessorKey: 'username', - enableSorting: false, + enableSorting: true, }, - columnHelper.display({ + { header: 'Role', - enableSorting: false, id: 'role', - cell: (props) => props.row.original.role.role, - }), + accessorFn: (user) => user.role.role, + enableSorting: true, + }, { header: 'Active', accessorKey: 'active', - enableSorting: false, + enableSorting: true, }, - columnHelper.display({ + { header: 'Created', - enableSorting: false, - id: '__createdtime__', + accessorKey: '__createdtime__', + enableSorting: true, + enableGlobalFilter: false, cell: (props) => translateSecondsToAgo( (Date.now() - props.row.original.__createdtime__) / 1000, props.row.original.__createdtime__, ), - }), + }, { header: 'Updated', accessorKey: '__updatedtime__', - enableSorting: false, + enableSorting: true, + enableGlobalFilter: false, cell: (props) => translateSecondsToAgo( (Date.now() - props.row.original.__updatedtime__) / 1000, diff --git a/src/features/instance/config/users/index.tsx b/src/features/instance/config/users/index.tsx index 82e47ec72..c3964d8f6 100644 --- a/src/features/instance/config/users/index.tsx +++ b/src/features/instance/config/users/index.tsx @@ -86,6 +86,8 @@ export function ConfigUsersIndex() { columns={dataTableColumns} onRowClick={onRowClick} sortingState={sortingState} + filter={{ label: 'Filter users', placeholder: 'Filter by username or role' }} + paginated > {/**/} {/*disabled={tableDataFetching}*/}