convent UsersList to tables

This commit is contained in:
Keith Grant
2021-01-27 15:32:59 -08:00
parent f747edca0c
commit e886ce57aa
4 changed files with 157 additions and 149 deletions

View File

@@ -7,7 +7,11 @@ import { UsersAPI } from '../../../api';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
import DataListToolbar from '../../../components/DataListToolbar'; import DataListToolbar from '../../../components/DataListToolbar';
import ErrorDetail from '../../../components/ErrorDetail'; import ErrorDetail from '../../../components/ErrorDetail';
import PaginatedDataList, { import PaginatedTable, {
HeaderRow,
HeaderCell,
} from '../../../components/PaginatedTable';
import {
ToolbarAddButton, ToolbarAddButton,
ToolbarDeleteButton, ToolbarDeleteButton,
} from '../../../components/PaginatedDataList'; } from '../../../components/PaginatedDataList';
@@ -101,7 +105,7 @@ function UserList({ i18n }) {
<> <>
<PageSection> <PageSection>
<Card> <Card>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={users} items={users}
@@ -124,20 +128,6 @@ function UserList({ i18n }) {
key: 'last_name__icontains', key: 'last_name__icontains',
}, },
]} ]}
toolbarSortColumns={[
{
name: i18n._(t`Username`),
key: 'username',
},
{
name: i18n._(t`First Name`),
key: 'first_name',
},
{
name: i18n._(t`Last Name`),
key: 'last_name',
},
]}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
@@ -167,13 +157,28 @@ function UserList({ i18n }) {
]} ]}
/> />
)} )}
renderItem={o => ( headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="username">
{i18n._(t`Username`)}
</HeaderCell>
<HeaderCell sortKey="first_name">
{i18n._(t`First Name`)}
</HeaderCell>
<HeaderCell sortKey="last_name">
{i18n._(t`Last Name`)}
</HeaderCell>
<HeaderCell>{i18n._(t`Role`)}</HeaderCell>
</HeaderRow>
}
renderRow={(user, index) => (
<UserListItem <UserListItem
key={o.id} key={user.id}
user={o} user={user}
detailUrl={`${match.url}/${o.id}/details`} detailUrl={`${match.url}/${user.id}/details`}
isSelected={selected.some(row => row.id === o.id)} isSelected={selected.some(row => row.id === user.id)}
onSelect={() => handleSelect(o)} onSelect={() => handleSelect(user)}
rowIndex={index}
/> />
)} )}
emptyStateControls={ emptyStateControls={

View File

@@ -129,51 +129,66 @@ describe('UsersList with full permissions', () => {
test('should check and uncheck the row item', async () => { test('should check and uncheck the row item', async () => {
expect( expect(
wrapper.find('DataListCheck[id="select-user-1"]').props().checked wrapper
.find('.pf-c-table__check input')
.first()
.props().checked
).toBe(false); ).toBe(false);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-user-1"]').invoke('onChange')( wrapper
true .find('.pf-c-table__check input')
); .first()
.invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-user-1"]').props().checked wrapper
.find('.pf-c-table__check input')
.first()
.props().checked
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-user-1"]').invoke('onChange')( wrapper
false .find('.pf-c-table__check input')
); .first()
.invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-user-1"]').props().checked wrapper
.find('.pf-c-table__check input')
.first()
.props().checked
).toBe(false); ).toBe(false);
}); });
test('should check all row items when select all is checked', async () => { test('should check all row items when select all is checked', async () => {
wrapper.find('DataListCheck').forEach(el => { expect(wrapper.find('.pf-c-table__check input')).toHaveLength(2);
wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(true); wrapper.find('Checkbox#select-all').invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListCheck').forEach(el => { wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(true); expect(el.props().checked).toBe(true);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(false); wrapper.find('Checkbox#select-all').invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListCheck').forEach(el => { wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
}); });
test('should call api delete users for each selected user', async () => { test('should call api delete users for each selected user', async () => {
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-user-1"]').invoke('onChange')(); wrapper
.find('.pf-c-table__check input')
.first()
.invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {
@@ -185,10 +200,12 @@ describe('UsersList with full permissions', () => {
test('should show error modal when user is not successfully deleted from api', async () => { test('should show error modal when user is not successfully deleted from api', async () => {
UsersAPI.destroy.mockImplementationOnce(() => Promise.reject(new Error())); UsersAPI.destroy.mockImplementationOnce(() => Promise.reject(new Error()));
// expect(wrapper.debug()).toBe(false);
expect(wrapper.find('Modal').length).toBe(0); expect(wrapper.find('Modal').length).toBe(0);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-user-1"]').invoke('onChange')(); wrapper
.find('.pf-c-table__check input')
.first()
.invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {

View File

@@ -3,24 +3,22 @@ import React, { Fragment } from 'react';
import { string, bool, func } from 'prop-types'; import { string, bool, func } from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import { Button, Label } from '@patternfly/react-core';
Button, import { Tr, Td } from '@patternfly/react-table';
DataListAction,
DataListCheck,
DataListItem,
DataListItemCells,
DataListItemRow,
Label,
Tooltip,
} from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '../../../components/DataListCell'; import { ActionsTd, ActionItem } from '../../../components/PaginatedTable';
import { User } from '../../../types'; import { User } from '../../../types';
function UserListItem({ user, isSelected, onSelect, detailUrl, i18n }) { function UserListItem({
user,
isSelected,
onSelect,
detailUrl,
rowIndex,
i18n,
}) {
const labelId = `check-action-${user.id}`; const labelId = `check-action-${user.id}`;
let user_type; let user_type;
@@ -36,84 +34,64 @@ function UserListItem({ user, isSelected, onSelect, detailUrl, i18n }) {
const socialAuthUser = user.auth.length > 0; const socialAuthUser = user.auth.length > 0;
return ( return (
<DataListItem key={user.id} aria-labelledby={labelId} id={`${user.id}`}> <Tr id={`user-row-${user.id}`}>
<DataListItemRow> <Td
<DataListCheck select={{
id={`select-user-${user.id}`} rowIndex,
checked={isSelected} isSelected,
onChange={onSelect} onSelect,
aria-labelledby={labelId} }}
/> />
<DataListItemCells <Td id={labelId} dataLabel={i18n._(t`Username`)}>
dataListCells={[ <Link to={`${detailUrl}`} id={labelId}>
<DataListCell key="username" aria-label={i18n._(t`username`)}> <b>{user.username}</b>
<span id={labelId}> </Link>
<Link to={`${detailUrl}`} id={labelId}> {ldapUser && (
<b>{user.username}</b> <span css="margin-left: 12px">
</Link> <Label aria-label={i18n._(t`ldap user`)}>{i18n._(t`LDAP`)}</Label>
</span> </span>
{ldapUser && ( )}
<span css="margin-left: 12px"> {socialAuthUser && (
<Label aria-label={i18n._(t`ldap user`)}> <span css="margin-left: 12px">
{i18n._(t`LDAP`)} <Label aria-label={i18n._(t`social login`)}>
</Label> {i18n._(t`SOCIAL`)}
</span> </Label>
)} </span>
{socialAuthUser && ( )}
<span css="margin-left: 12px"> </Td>
<Label aria-label={i18n._(t`social login`)}> <Td dataLabel={i18n._(t`First Name`)}>
{i18n._(t`SOCIAL`)} {user.first_name && (
</Label> <Fragment>
</span> <b css="margin-right: 24px">{i18n._(t`First Name`)}</b>
)} {user.first_name}
</DataListCell>, </Fragment>
<DataListCell )}
key="first-name" </Td>
aria-label={i18n._(t`user first name`)} <Td dataLabel={i18n._(t`Last Name`)}>
> {user.last_name && (
{user.first_name && ( <Fragment>
<Fragment> <b css="margin-right: 24px">{i18n._(t`Last Name`)}</b>
<b css="margin-right: 24px">{i18n._(t`First Name`)}</b> {user.last_name}
{user.first_name} </Fragment>
</Fragment> )}
)} </Td>
</DataListCell>, <Td dataLabel={i18n._(t`Role`)}>{user_type}</Td>
<DataListCell <ActionsTd dataLabel={i18n._(t`Actions`)}>
key="last-name" <ActionItem
aria-label={i18n._(t`user last name`)} visible={user.summary_fields.user_capabilities.edit}
> tooltip={i18n._(t`Edit User`)}
{user.last_name && (
<Fragment>
<b css="margin-right: 24px">{i18n._(t`Last Name`)}</b>
{user.last_name}
</Fragment>
)}
</DataListCell>,
<DataListCell key="user-type" aria-label={i18n._(t`user type`)}>
{user_type}
</DataListCell>,
]}
/>
<DataListAction
aria-label={i18n._(t`actions`)}
aria-labelledby={labelId}
id={labelId}
> >
{user.summary_fields.user_capabilities.edit && ( <Button
<Tooltip content={i18n._(t`Edit User`)} position="top"> aria-label={i18n._(t`Edit User`)}
<Button variant="plain"
aria-label={i18n._(t`Edit User`)} component={Link}
variant="plain" to={`/users/${user.id}/edit`}
component={Link} >
to={`/users/${user.id}/edit`} <PencilAltIcon />
> </Button>
<PencilAltIcon /> </ActionItem>
</Button> </ActionsTd>
</Tooltip> </Tr>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
); );
} }

View File

@@ -18,12 +18,16 @@ describe('UserListItem with full permissions', () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<I18nProvider> <I18nProvider>
<MemoryRouter initialEntries={['/users']} initialIndex={0}> <MemoryRouter initialEntries={['/users']} initialIndex={0}>
<UserListItem <table>
user={mockDetails} <tbody>
detailUrl="/user/1" <UserListItem
isSelected user={mockDetails}
onSelect={() => {}} detailUrl="/user/1"
/> isSelected
onSelect={() => {}}
/>
</tbody>
</table>
</MemoryRouter> </MemoryRouter>
</I18nProvider> </I18nProvider>
); );
@@ -36,9 +40,9 @@ describe('UserListItem with full permissions', () => {
}); });
test('should display user data', () => { test('should display user data', () => {
expect( expect(wrapper.find('td[data-label="Role"]').prop('children')).toEqual(
wrapper.find('DataListCell[aria-label="user type"]').prop('children') 'System Administrator'
).toEqual('System Administrator'); );
expect( expect(
wrapper.find('Label[aria-label="social login"]').prop('children') wrapper.find('Label[aria-label="social login"]').prop('children')
).toEqual('SOCIAL'); ).toEqual('SOCIAL');
@@ -50,19 +54,23 @@ describe('UserListItem without full permissions', () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<I18nProvider> <I18nProvider>
<MemoryRouter initialEntries={['/users']} initialIndex={0}> <MemoryRouter initialEntries={['/users']} initialIndex={0}>
<UserListItem <table>
user={{ <tbody>
...mockDetails, <UserListItem
summary_fields: { user={{
user_capabilities: { ...mockDetails,
edit: false, summary_fields: {
}, user_capabilities: {
}, edit: false,
}} },
detailUrl="/user/1" },
isSelected }}
onSelect={() => {}} detailUrl="/user/1"
/> isSelected
onSelect={() => {}}
/>
</tbody>
</table>
</MemoryRouter> </MemoryRouter>
</I18nProvider> </I18nProvider>
); );