convert InventoryList to use PaginatedTable

This commit is contained in:
Keith Grant
2020-12-01 13:59:05 -08:00
parent 1816280a15
commit a8159273eb
7 changed files with 114 additions and 192 deletions

View File

@@ -93,9 +93,11 @@ function DataListToolbar({
onRemove={onRemove} onRemove={onRemove}
/> />
</ToolbarItem> </ToolbarItem>
<ToolbarItem> {sortColumns && (
<Sort qsConfig={qsConfig} columns={sortColumns} onSort={onSort} /> <ToolbarItem>
</ToolbarItem> <Sort qsConfig={qsConfig} columns={sortColumns} onSort={onSort} />
</ToolbarItem>
)}
</ToolbarToggleGroup> </ToolbarToggleGroup>
{showExpandCollapse && ( {showExpandCollapse && (
<ToolbarGroup> <ToolbarGroup>

View File

@@ -7,21 +7,13 @@ import {
replaceParams, replaceParams,
} from '../../util/qs'; } from '../../util/qs';
export default function HeaderRow({ export default function HeaderRow({ qsConfig, defaultSortKey, children }) {
handleSelectAll,
isAllSelected,
qsConfig,
defaultSortKey,
children,
}) {
const location = useLocation(); const location = useLocation();
const history = useHistory(); const history = useHistory();
const params = parseQueryString(qsConfig, location.search); const params = parseQueryString(qsConfig, location.search);
// TODO: asc vs desc -- correct for both alpha & numeric sorting?
const onSort = (key, order) => { const onSort = (key, order) => {
console.log({ key, order });
const newParams = replaceParams(params, { const newParams = replaceParams(params, {
order_by: order === 'asc' ? key : `-${key}`, order_by: order === 'asc' ? key : `-${key}`,
page: null, page: null,
@@ -43,12 +35,7 @@ export default function HeaderRow({
return ( return (
<Thead> <Thead>
<Tr> <Tr>
<Th <Th />
select={{
onSelect: handleSelectAll,
isSelected: isAllSelected,
}}
/>
{React.Children.map(children, child => {React.Children.map(children, child =>
React.cloneElement(child, { React.cloneElement(child, {
onSort, onSort,

View File

@@ -18,7 +18,7 @@ import {
replaceParams, replaceParams,
} from '../../util/qs'; } from '../../util/qs';
import PaginatedTableRow from './PaginatedTableRow'; import PaginatedTableRow from './PaginatedTableRow';
import { QSConfig, SearchColumns, SortColumns } from '../../types'; import { QSConfig, SearchColumns } from '../../types';
function PaginatedTable({ function PaginatedTable({
contentError, contentError,
@@ -32,7 +32,6 @@ function PaginatedTable({
toolbarSearchColumns, toolbarSearchColumns,
toolbarSearchableKeys, toolbarSearchableKeys,
toolbarRelatedSearchableKeys, toolbarRelatedSearchableKeys,
toolbarSortColumns,
pluralizedItemName, pluralizedItemName,
showPageSizeOptions, showPageSizeOptions,
i18n, i18n,
@@ -71,14 +70,6 @@ function PaginatedTable({
isDefault: true, isDefault: true,
}, },
]; ];
const sortColumns = toolbarSortColumns.length
? toolbarSortColumns
: [
{
name: i18n._(t`Name`),
key: 'name',
},
];
const queryParams = parseQueryString(qsConfig, history.location.search); const queryParams = parseQueryString(qsConfig, history.location.search);
const dataListLabel = i18n._(t`${pluralizedItemName} List`); const dataListLabel = i18n._(t`${pluralizedItemName} List`);
@@ -98,10 +89,7 @@ function PaginatedTable({
); );
} else { } else {
Content = ( Content = (
<TableComposable <TableComposable aria-label={dataListLabel}>
aria-label={dataListLabel}
// onSelectDataListItem={handleListItemSelect}
>
{headerRow} {headerRow}
<Tbody>{items.map(renderRow)}</Tbody> <Tbody>{items.map(renderRow)}</Tbody>
</TableComposable> </TableComposable>
@@ -137,7 +125,6 @@ function PaginatedTable({
renderToolbar={renderToolbar} renderToolbar={renderToolbar}
emptyStateControls={emptyStateControls} emptyStateControls={emptyStateControls}
searchColumns={searchColumns} searchColumns={searchColumns}
sortColumns={sortColumns}
searchableKeys={toolbarSearchableKeys} searchableKeys={toolbarSearchableKeys}
relatedSearchableKeys={toolbarRelatedSearchableKeys} relatedSearchableKeys={toolbarRelatedSearchableKeys}
qsConfig={qsConfig} qsConfig={qsConfig}
@@ -183,7 +170,6 @@ PaginatedTable.propTypes = {
toolbarSearchColumns: SearchColumns, toolbarSearchColumns: SearchColumns,
toolbarSearchableKeys: PropTypes.arrayOf(PropTypes.string), toolbarSearchableKeys: PropTypes.arrayOf(PropTypes.string),
toolbarRelatedSearchableKeys: PropTypes.arrayOf(PropTypes.string), toolbarRelatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
toolbarSortColumns: SortColumns,
showPageSizeOptions: PropTypes.bool, showPageSizeOptions: PropTypes.bool,
renderToolbar: PropTypes.func, renderToolbar: PropTypes.func,
hasContentLoading: PropTypes.bool, hasContentLoading: PropTypes.bool,
@@ -197,7 +183,6 @@ PaginatedTable.defaultProps = {
toolbarSearchColumns: [], toolbarSearchColumns: [],
toolbarSearchableKeys: [], toolbarSearchableKeys: [],
toolbarRelatedSearchableKeys: [], toolbarRelatedSearchableKeys: [],
toolbarSortColumns: [],
pluralizedItemName: 'Items', pluralizedItemName: 'Items',
showPageSizeOptions: true, showPageSizeOptions: true,
renderRow: item => <PaginatedTableRow key={item.id} item={item} />, renderRow: item => <PaginatedTableRow key={item.id} item={item} />,

View File

@@ -8,9 +8,11 @@ import useRequest, { useDeleteItems } from '../../../util/useRequest';
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 { ToolbarDeleteButton } from '../../../components/PaginatedDataList';
ToolbarDeleteButton, import PaginatedTable, {
} from '../../../components/PaginatedDataList'; HeaderRow,
HeaderCell,
} from '../../../components/PaginatedTable';
import { getQSConfig, parseQueryString } from '../../../util/qs'; import { getQSConfig, parseQueryString } from '../../../util/qs';
import useWsInventories from './useWsInventories'; import useWsInventories from './useWsInventories';
import AddDropDownButton from '../../../components/AddDropDownButton'; import AddDropDownButton from '../../../components/AddDropDownButton';
@@ -149,10 +151,11 @@ function InventoryList({ i18n }) {
]} ]}
/> />
); );
return ( return (
<PageSection> <PageSection>
<Card> <Card>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={inventories} items={inventories}
@@ -187,6 +190,17 @@ function InventoryList({ i18n }) {
]} ]}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={
<HeaderRow defaultSortKey="name" qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
<HeaderCell>{i18n._(t`Status`)}</HeaderCell>
<HeaderCell>{i18n._(t`Type`)}</HeaderCell>
<HeaderCell>{i18n._(t`Organization`)}</HeaderCell>
<HeaderCell>{i18n._(t`Groups`)}</HeaderCell>
<HeaderCell>{i18n._(t`Hosts`)}</HeaderCell>
<HeaderCell>{i18n._(t`Sources`)}</HeaderCell>
</HeaderRow>
}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}
@@ -209,11 +223,12 @@ function InventoryList({ i18n }) {
]} ]}
/> />
)} )}
renderItem={inventory => ( renderRow={(inventory, index) => (
<InventoryListItem <InventoryListItem
key={inventory.id} key={inventory.id}
value={inventory.name} value={inventory.name}
inventory={inventory} inventory={inventory}
rowIndex={index}
fetchInventories={fetchInventories} fetchInventories={fetchInventories}
detailUrl={ detailUrl={
inventory.kind === 'smart' inventory.kind === 'smart'

View File

@@ -1,50 +1,21 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } 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 { import { Button, Label, Tooltip } from '@patternfly/react-core';
Button, import { Tr, Td } from '@patternfly/react-table';
DataListAction as _DataListAction,
DataListCheck,
DataListItem,
DataListItemCells,
DataListItemRow,
Label,
Tooltip,
Badge as PFBadge,
} from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { timeOfDay } from '../../../util/dates'; import { timeOfDay } from '../../../util/dates';
import { InventoriesAPI } from '../../../api'; import { InventoriesAPI } from '../../../api';
import { Inventory } from '../../../types'; import { Inventory } from '../../../types';
import DataListCell from '../../../components/DataListCell'; import { ActionsTd } from '../../../components/PaginatedTable';
import CopyButton from '../../../components/CopyButton'; import CopyButton from '../../../components/CopyButton';
import SyncStatusIndicator from '../../../components/SyncStatusIndicator'; import SyncStatusIndicator from '../../../components/SyncStatusIndicator';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 16px;
grid-template-columns: repeat(2, 40px);
`;
const Badge = styled(PFBadge)`
margin-left: 8px;
`;
const ListGroup = styled.div`
margin-left: 8px;
display: inline-block;
`;
const OrgLabel = styled.b`
margin-right: 20px;
`;
function InventoryListItem({ function InventoryListItem({
inventory, inventory,
rowIndex,
isSelected, isSelected,
onSelect, onSelect,
detailUrl, detailUrl,
@@ -85,108 +56,81 @@ function InventoryListItem({
} }
return ( return (
<DataListItem <Tr id={inventory.id}>
key={inventory.id} <Td
aria-labelledby={labelId} select={{
id={`${inventory.id}`} rowIndex,
> isSelected,
<DataListItemRow> onSelect,
<DataListCheck }}
id={`select-inventory-${inventory.id}`} />
isDisabled={inventory.pending_deletion} <Td id={labelId}>
checked={isSelected} {inventory.pending_deletion ? (
onChange={onSelect} <b>{inventory.name}</b>
aria-labelledby={labelId} ) : (
/> <Link to={`${detailUrl}`}>
<DataListItemCells <b>{inventory.name}</b>
dataListCells={[ </Link>
<DataListCell key="sync-status" isIcon>
{inventory.kind !== 'smart' && (
<SyncStatusIndicator status={syncStatus} />
)}
</DataListCell>,
<DataListCell key="name">
{inventory.pending_deletion ? (
<b>{inventory.name}</b>
) : (
<Link to={`${detailUrl}`}>
<b>{inventory.name}</b>
</Link>
)}
</DataListCell>,
<DataListCell key="kind">
{inventory.kind === 'smart'
? i18n._(t`Smart Inventory`)
: i18n._(t`Inventory`)}
</DataListCell>,
<DataListCell key="organization">
<OrgLabel>{i18n._(t`Organization`)}</OrgLabel>
<Link
to={`/organizations/${inventory.summary_fields.organization.id}/details`}
>
{inventory.summary_fields.organization.name}
</Link>
</DataListCell>,
<DataListCell key="groups-hosts-sources-counts">
<ListGroup>
{i18n._(t`Groups`)}
<Badge isRead>{inventory.total_groups}</Badge>
</ListGroup>
<ListGroup>
{i18n._(t`Hosts`)}
<Badge isRead>{inventory.total_hosts}</Badge>
</ListGroup>
<ListGroup>
{i18n._(t`Sources`)}
<Badge isRead>{inventory.total_inventory_sources}</Badge>
</ListGroup>
</DataListCell>,
inventory.pending_deletion && (
<DataListCell alignRight isFilled={false} key="pending-delete">
<Label color="red">{i18n._(t`Pending delete`)}</Label>
</DataListCell>
),
]}
/>
{!inventory.pending_deletion && (
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
{inventory.summary_fields.user_capabilities.edit ? (
<Tooltip content={i18n._(t`Edit Inventory`)} position="top">
<Button
isDisabled={isDisabled}
aria-label={i18n._(t`Edit Inventory`)}
variant="plain"
component={Link}
to={`/inventories/${
inventory.kind === 'smart' ? 'smart_inventory' : 'inventory'
}/${inventory.id}/edit`}
>
<PencilAltIcon />
</Button>
</Tooltip>
) : (
''
)}
{inventory.summary_fields.user_capabilities.copy && (
<CopyButton
copyItem={copyInventory}
isDisabled={isDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
helperText={{
tooltip: i18n._(t`Copy Inventory`),
errorMessage: i18n._(t`Failed to copy inventory.`),
}}
/>
)}
</DataListAction>
)} )}
</DataListItemRow> </Td>
</DataListItem> <Td>
{inventory.kind !== 'smart' && (
<SyncStatusIndicator status={syncStatus} />
)}
</Td>
<Td>
{inventory.kind === 'smart'
? i18n._(t`Smart Inventory`)
: i18n._(t`Inventory`)}
</Td>
<Td key="organization">
<Link
to={`/organizations/${inventory.summary_fields.organization.id}/details`}
>
{inventory.summary_fields.organization.name}
</Link>
</Td>
<Td>{inventory.total_groups}</Td>
<Td>{inventory.total_hosts}</Td>
<Td>{inventory.total_inventory_sources}</Td>
{inventory.pending_deletion ? (
<Td>
<Label color="red">{i18n._(t`Pending delete`)}</Label>
</Td>
) : (
<ActionsTd numActions={2}>
{inventory.summary_fields.user_capabilities.edit ? (
<Tooltip content={i18n._(t`Edit Inventory`)} position="top">
<Button
isDisabled={isDisabled}
aria-label={i18n._(t`Edit Inventory`)}
variant="plain"
component={Link}
to={`/inventories/${
inventory.kind === 'smart' ? 'smart_inventory' : 'inventory'
}/${inventory.id}/edit`}
>
<PencilAltIcon />
</Button>
</Tooltip>
) : (
<div />
)}
{inventory.summary_fields.user_capabilities.copy && (
<CopyButton
copyItem={copyInventory}
isDisabled={isDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
helperText={{
tooltip: i18n._(t`Copy Inventory`),
errorMessage: i18n._(t`Failed to copy inventory.`),
}}
/>
)}
</ActionsTd>
)}
</Tr>
); );
} }
export default withI18n()(InventoryListItem); export default withI18n()(InventoryListItem);

View File

@@ -122,12 +122,13 @@ function OrganizationsList({ i18n }) {
<PageSection> <PageSection>
<Card> <Card>
<PaginatedTable <PaginatedTable
// TODO: audit if any of these props are no longer in use
contentError={contentError} contentError={contentError}
hasContentLoading={hasContentLoading} hasContentLoading={hasContentLoading}
items={organizations} items={organizations}
itemCount={organizationCount} itemCount={organizationCount}
pluralizedItemName={i18n._(t`Organizations`)} pluralizedItemName={i18n._(t`Organizations`)}
qsConfig={QS_CONFIG} // TODO: still used? qsConfig={QS_CONFIG}
onRowClick={handleSelect} onRowClick={handleSelect}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
@@ -148,22 +149,10 @@ function OrganizationsList({ i18n }) {
key: 'modified_by__username__icontains', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={ headerRow={
// TODO: move selectAll logic into HeaderRow? <HeaderRow defaultSortKey="name" qsConfig={QS_CONFIG}>
<HeaderRow
handleSelectAll={handleSelectAll}
isAllSelected={isAllSelected}
defaultSortKey="name"
qsConfig={QS_CONFIG}
>
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell> <HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
<HeaderCell>{i18n._(t`Members`)}</HeaderCell> <HeaderCell>{i18n._(t`Members`)}</HeaderCell>
<HeaderCell>{i18n._(t`Teams`)}</HeaderCell> <HeaderCell>{i18n._(t`Teams`)}</HeaderCell>
@@ -172,9 +161,9 @@ function OrganizationsList({ i18n }) {
renderToolbar={props => ( renderToolbar={props => (
<DataListToolbar <DataListToolbar
{...props} {...props}
// showSelectAll showSelectAll
// isAllSelected={isAllSelected} isAllSelected={isAllSelected}
// onSelectAll={handleSelectAll} onSelectAll={handleSelectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd

View File

@@ -36,7 +36,7 @@ function OrganizationListItem({
</Td> </Td>
<Td>{organization.summary_fields.related_field_counts.users}</Td> <Td>{organization.summary_fields.related_field_counts.users}</Td>
<Td>{organization.summary_fields.related_field_counts.teams}</Td> <Td>{organization.summary_fields.related_field_counts.teams}</Td>
<ActionsTd numActions={2}> <ActionsTd numActions={1}>
{organization.summary_fields.user_capabilities.edit ? ( {organization.summary_fields.user_capabilities.edit ? (
<Tooltip content={i18n._(t`Edit Organization`)} position="top"> <Tooltip content={i18n._(t`Edit Organization`)} position="top">
<Button <Button