diff --git a/awx/ui_next/src/screens/Host/HostList/HostList.jsx b/awx/ui_next/src/screens/Host/HostList/HostList.jsx index 0fabce1b60..684d195ac9 100644 --- a/awx/ui_next/src/screens/Host/HostList/HostList.jsx +++ b/awx/ui_next/src/screens/Host/HostList/HostList.jsx @@ -8,10 +8,14 @@ import { HostsAPI } from '../../../api'; import AlertModal from '../../../components/AlertModal'; import DataListToolbar from '../../../components/DataListToolbar'; import ErrorDetail from '../../../components/ErrorDetail'; -import PaginatedDataList, { +import { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; +import PaginatedTable, { + HeaderRow, + HeaderCell, +} from '../../../components/PaginatedTable'; import useRequest, { useDeleteItems } from '../../../util/useRequest'; import { encodeQueryString, @@ -130,7 +134,7 @@ function HostList({ i18n }) { return ( - + {i18n._(t`Name`)} + {i18n._(t`Inventory`)} + {i18n._(t`Actions`)} + + } renderToolbar={props => ( )} - renderItem={host => ( + renderRow={(host, index) => ( row.id === host.id)} onSelect={() => handleSelect(host)} + rowIndex={index} /> )} emptyStateControls={ diff --git a/awx/ui_next/src/screens/Host/HostList/HostList.test.jsx b/awx/ui_next/src/screens/Host/HostList/HostList.test.jsx index 5b502979e1..4212a08ff5 100644 --- a/awx/ui_next/src/screens/Host/HostList/HostList.test.jsx +++ b/awx/ui_next/src/screens/Host/HostList/HostList.test.jsx @@ -134,8 +134,9 @@ describe('', () => { act(() => { wrapper - .find('input#select-host-1') - .closest('DataListCheck') + .find('.pf-c-table__check') + .first() + .find('input') .invoke('onChange')(); }); wrapper.update(); @@ -147,8 +148,9 @@ describe('', () => { ).toEqual(true); act(() => { wrapper - .find('input#select-host-1') - .closest('DataListCheck') + .find('.pf-c-table__check') + .first() + .find('input') .invoke('onChange')(); }); wrapper.update(); diff --git a/awx/ui_next/src/screens/Host/HostList/HostListItem.jsx b/awx/ui_next/src/screens/Host/HostList/HostListItem.jsx index d920568f96..87c4d4eff6 100644 --- a/awx/ui_next/src/screens/Host/HostList/HostListItem.jsx +++ b/awx/ui_next/src/screens/Host/HostList/HostListItem.jsx @@ -1,91 +1,67 @@ import 'styled-components/macro'; -import React, { Fragment } from 'react'; +import React from 'react'; import { string, bool, func } from 'prop-types'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { - Button, - DataListAction as _DataListAction, - DataListCheck, - DataListItem, - DataListItemRow, - DataListItemCells, - Tooltip, -} from '@patternfly/react-core'; +import { Button } from '@patternfly/react-core'; +import { Tr, Td } from '@patternfly/react-table'; import { Link } from 'react-router-dom'; import { PencilAltIcon } from '@patternfly/react-icons'; -import styled from 'styled-components'; -import DataListCell from '../../../components/DataListCell'; - -import Sparkline from '../../../components/Sparkline'; +import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; import { Host } from '../../../types'; import HostToggle from '../../../components/HostToggle'; -const DataListAction = styled(_DataListAction)` - align-items: center; - display: grid; - grid-gap: 24px; - grid-template-columns: 92px 40px; -`; - -function HostListItem({ i18n, host, isSelected, onSelect, detailUrl }) { +function HostListItem({ + i18n, + host, + isSelected, + onSelect, + detailUrl, + rowIndex, +}) { const labelId = `check-action-${host.id}`; + return ( - - - - - - {host.name} - - , - - - , - - {host.summary_fields.inventory && ( - - {i18n._(t`Inventory`)} - - {host.summary_fields.inventory.name} - - - )} - , - ]} - /> - + + + + {host.name} + + + + {host.summary_fields.inventory && ( + + {host.summary_fields.inventory.name} + + )} + + + + - - {host.summary_fields.user_capabilities.edit ? ( - - - - ) : ( - '' - )} - - - + + + + ); } diff --git a/awx/ui_next/src/screens/Host/HostList/HostListItem.test.jsx b/awx/ui_next/src/screens/Host/HostList/HostListItem.test.jsx index aee5b91127..15d6db5ab1 100644 --- a/awx/ui_next/src/screens/Host/HostList/HostListItem.test.jsx +++ b/awx/ui_next/src/screens/Host/HostList/HostListItem.test.jsx @@ -25,12 +25,16 @@ describe('', () => { beforeEach(() => { wrapper = mountWithContexts( - {}} - host={mockHost} - /> + + + {}} + host={mockHost} + /> + +
); }); @@ -46,12 +50,16 @@ describe('', () => { const copyMockHost = Object.assign({}, mockHost); copyMockHost.summary_fields.user_capabilities.edit = false; wrapper = mountWithContexts( - {}} - host={copyMockHost} - /> + + + {}} + host={copyMockHost} + /> + +
); expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); }); diff --git a/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx b/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx index e128e9890a..3e91b5a226 100644 --- a/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx +++ b/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx @@ -9,7 +9,11 @@ import useRequest, { useDeleteItems } from '../../../util/useRequest'; import AlertModal from '../../../components/AlertModal'; import DataListToolbar from '../../../components/DataListToolbar'; import ErrorDetail from '../../../components/ErrorDetail'; -import PaginatedDataList, { +import PaginatedTable, { + HeaderRow, + HeaderCell, +} from '../../../components/PaginatedTable'; +import { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; @@ -112,7 +116,7 @@ function TeamList({ i18n }) { - + {i18n._(t`Name`)} + {i18n._(t`Organization`)} + {i18n._(t`Actions`)} + + } renderToolbar={props => ( )} - renderItem={o => ( + renderRow={(team, index) => ( row.id === o.id)} - onSelect={() => handleSelect(o)} + key={team.id} + team={team} + detailUrl={`${match.url}/${team.id}`} + isSelected={selected.some(row => row.id === team.id)} + onSelect={() => handleSelect(team)} + rowIndex={index} /> )} emptyStateControls={ diff --git a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx index 0dfe158951..a6f56eee3c 100644 --- a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx +++ b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx @@ -1,33 +1,23 @@ import 'styled-components/macro'; -import React, { Fragment } from 'react'; +import React from 'react'; import { string, bool, func } from 'prop-types'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { - Button, - DataListAction as _DataListAction, - DataListCheck, - DataListItem, - DataListItemCells, - DataListItemRow, - Tooltip, -} from '@patternfly/react-core'; - -import styled from 'styled-components'; +import { Button } from '@patternfly/react-core'; +import { Tr, Td } from '@patternfly/react-table'; import { Link } from 'react-router-dom'; import { PencilAltIcon } from '@patternfly/react-icons'; -import DataListCell from '../../../components/DataListCell'; - +import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; import { Team } from '../../../types'; -const DataListAction = styled(_DataListAction)` - align-items: center; - display: grid; - grid-gap: 16px; - grid-template-columns: 40px; -`; - -function TeamListItem({ team, isSelected, onSelect, detailUrl, i18n }) { +function TeamListItem({ + team, + isSelected, + onSelect, + detailUrl, + rowIndex, + i18n, +}) { TeamListItem.propTypes = { team: Team.isRequired, detailUrl: string.isRequired, @@ -38,57 +28,45 @@ function TeamListItem({ team, isSelected, onSelect, detailUrl, i18n }) { const labelId = `check-action-${team.id}`; return ( - - - - - - {team.name} - - , - - {team.summary_fields.organization && ( - - {i18n._(t`Organization`)}{' '} - - {team.summary_fields.organization.name} - - - )} - , - ]} - /> - + + + + {team.name} + + + + {team.summary_fields.organization && ( + + {team.summary_fields.organization.name} + + )} + + + - {team.summary_fields.user_capabilities.edit ? ( - - - - ) : ( - '' - )} - - - + + + + ); } export default withI18n()(TeamListItem); diff --git a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.test.jsx b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.test.jsx index ffc2af1f35..377d37184e 100644 --- a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.test.jsx +++ b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.test.jsx @@ -11,20 +11,24 @@ describe('', () => { mountWithContexts( - {}} - /> + + + {}} + /> + +
); @@ -33,20 +37,24 @@ describe('', () => { const wrapper = mountWithContexts( - {}} - /> + + + {}} + /> + +
); @@ -56,20 +64,24 @@ describe('', () => { const wrapper = mountWithContexts( - {}} - /> + + + {}} + /> + +
); diff --git a/awx/ui_next/src/screens/User/UserList/UserList.jsx b/awx/ui_next/src/screens/User/UserList/UserList.jsx index 06c1d3ae1c..9a48938744 100644 --- a/awx/ui_next/src/screens/User/UserList/UserList.jsx +++ b/awx/ui_next/src/screens/User/UserList/UserList.jsx @@ -7,7 +7,11 @@ import { UsersAPI } from '../../../api'; import AlertModal from '../../../components/AlertModal'; import DataListToolbar from '../../../components/DataListToolbar'; import ErrorDetail from '../../../components/ErrorDetail'; -import PaginatedDataList, { +import PaginatedTable, { + HeaderRow, + HeaderCell, +} from '../../../components/PaginatedTable'; +import { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; @@ -101,7 +105,7 @@ function UserList({ i18n }) { <> - ( @@ -167,13 +157,29 @@ function UserList({ i18n }) { ]} /> )} - renderItem={o => ( + headerRow={ + + + {i18n._(t`Username`)} + + + {i18n._(t`First Name`)} + + + {i18n._(t`Last Name`)} + + {i18n._(t`Role`)} + {i18n._(t`Actions`)} + + } + renderRow={(user, index) => ( row.id === o.id)} - onSelect={() => handleSelect(o)} + key={user.id} + user={user} + detailUrl={`${match.url}/${user.id}/details`} + isSelected={selected.some(row => row.id === user.id)} + onSelect={() => handleSelect(user)} + rowIndex={index} /> )} emptyStateControls={ diff --git a/awx/ui_next/src/screens/User/UserList/UserList.test.jsx b/awx/ui_next/src/screens/User/UserList/UserList.test.jsx index 46cf02c128..21efc99d9c 100644 --- a/awx/ui_next/src/screens/User/UserList/UserList.test.jsx +++ b/awx/ui_next/src/screens/User/UserList/UserList.test.jsx @@ -129,51 +129,66 @@ describe('UsersList with full permissions', () => { test('should check and uncheck the row item', async () => { expect( - wrapper.find('DataListCheck[id="select-user-1"]').props().checked + wrapper + .find('.pf-c-table__check input') + .first() + .props().checked ).toBe(false); await act(async () => { - wrapper.find('DataListCheck[id="select-user-1"]').invoke('onChange')( - true - ); + wrapper + .find('.pf-c-table__check input') + .first() + .invoke('onChange')(true); }); wrapper.update(); expect( - wrapper.find('DataListCheck[id="select-user-1"]').props().checked + wrapper + .find('.pf-c-table__check input') + .first() + .props().checked ).toBe(true); await act(async () => { - wrapper.find('DataListCheck[id="select-user-1"]').invoke('onChange')( - false - ); + wrapper + .find('.pf-c-table__check input') + .first() + .invoke('onChange')(false); }); wrapper.update(); expect( - wrapper.find('DataListCheck[id="select-user-1"]').props().checked + wrapper + .find('.pf-c-table__check input') + .first() + .props().checked ).toBe(false); }); 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); }); await act(async () => { wrapper.find('Checkbox#select-all').invoke('onChange')(true); }); wrapper.update(); - wrapper.find('DataListCheck').forEach(el => { + wrapper.find('.pf-c-table__check input').forEach(el => { expect(el.props().checked).toBe(true); }); await act(async () => { wrapper.find('Checkbox#select-all').invoke('onChange')(false); }); wrapper.update(); - wrapper.find('DataListCheck').forEach(el => { + wrapper.find('.pf-c-table__check input').forEach(el => { expect(el.props().checked).toBe(false); }); }); test('should call api delete users for each selected user', 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(); 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 () => { UsersAPI.destroy.mockImplementationOnce(() => Promise.reject(new Error())); - // expect(wrapper.debug()).toBe(false); expect(wrapper.find('Modal').length).toBe(0); await act(async () => { - wrapper.find('DataListCheck[id="select-user-1"]').invoke('onChange')(); + wrapper + .find('.pf-c-table__check input') + .first() + .invoke('onChange')(); }); wrapper.update(); await act(async () => { diff --git a/awx/ui_next/src/screens/User/UserList/UserListItem.jsx b/awx/ui_next/src/screens/User/UserList/UserListItem.jsx index c479313e71..ba4cc016fe 100644 --- a/awx/ui_next/src/screens/User/UserList/UserListItem.jsx +++ b/awx/ui_next/src/screens/User/UserList/UserListItem.jsx @@ -3,24 +3,22 @@ import React, { Fragment } from 'react'; import { string, bool, func } from 'prop-types'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { - Button, - DataListAction, - DataListCheck, - DataListItem, - DataListItemCells, - DataListItemRow, - Label, - Tooltip, -} from '@patternfly/react-core'; - +import { Button, Label } from '@patternfly/react-core'; +import { Tr, Td } from '@patternfly/react-table'; import { Link } from 'react-router-dom'; import { PencilAltIcon } from '@patternfly/react-icons'; -import DataListCell from '../../../components/DataListCell'; +import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; 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}`; let user_type; @@ -36,84 +34,64 @@ function UserListItem({ user, isSelected, onSelect, detailUrl, i18n }) { const socialAuthUser = user.auth.length > 0; return ( - - - - - - - {user.username} - - - {ldapUser && ( - - - - )} - {socialAuthUser && ( - - - - )} - , - - {user.first_name && ( - - {i18n._(t`First Name`)} - {user.first_name} - - )} - , - - {user.last_name && ( - - {i18n._(t`Last Name`)} - {user.last_name} - - )} - , - - {user_type} - , - ]} - /> - + + + + {user.username} + + {ldapUser && ( + + + + )} + {socialAuthUser && ( + + + + )} + + + {user.first_name && ( + + {i18n._(t`First Name`)} + {user.first_name} + + )} + + + {user.last_name && ( + + {i18n._(t`Last Name`)} + {user.last_name} + + )} + + {user_type} + + - {user.summary_fields.user_capabilities.edit && ( - - - - )} - - - + + + + ); } diff --git a/awx/ui_next/src/screens/User/UserList/UserListItem.test.jsx b/awx/ui_next/src/screens/User/UserList/UserListItem.test.jsx index fd14bdc09b..062c8b6c0f 100644 --- a/awx/ui_next/src/screens/User/UserList/UserListItem.test.jsx +++ b/awx/ui_next/src/screens/User/UserList/UserListItem.test.jsx @@ -18,12 +18,16 @@ describe('UserListItem with full permissions', () => { wrapper = mountWithContexts( - {}} - /> + + + {}} + /> + +
); @@ -36,9 +40,9 @@ describe('UserListItem with full permissions', () => { }); test('should display user data', () => { - expect( - wrapper.find('DataListCell[aria-label="user type"]').prop('children') - ).toEqual('System Administrator'); + expect(wrapper.find('td[data-label="Role"]').prop('children')).toEqual( + 'System Administrator' + ); expect( wrapper.find('Label[aria-label="social login"]').prop('children') ).toEqual('SOCIAL'); @@ -50,19 +54,23 @@ describe('UserListItem without full permissions', () => { wrapper = mountWithContexts( - {}} - /> + + + {}} + /> + +
);