mirror of
https://github.com/ansible/awx.git
synced 2026-01-15 11:50:42 -03:30
update search and sort column configuration
This commit is contained in:
parent
16f9411914
commit
8b9810e466
@ -142,21 +142,33 @@ class AddResourceRole extends React.Component {
|
||||
} = this.state;
|
||||
const { onClose, roles, i18n } = this.props;
|
||||
|
||||
const userColumns = [
|
||||
const userSearchColumns = [
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
isDefault: true
|
||||
},
|
||||
];
|
||||
|
||||
const teamColumns = [
|
||||
const userSortColumns = [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
},
|
||||
];
|
||||
|
||||
const teamSearchColumns = [
|
||||
{
|
||||
name: i18n._(t`name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
];
|
||||
|
||||
const teamSortColumns = [
|
||||
{
|
||||
name: i18n._(t`name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
];
|
||||
|
||||
@ -207,7 +219,8 @@ class AddResourceRole extends React.Component {
|
||||
<Fragment>
|
||||
{selectedResource === 'users' && (
|
||||
<SelectResourceStep
|
||||
columns={userColumns}
|
||||
searchColumns={userSearchColumns}
|
||||
sortColumns={userSortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={readUsers}
|
||||
@ -218,7 +231,8 @@ class AddResourceRole extends React.Component {
|
||||
)}
|
||||
{selectedResource === 'teams' && (
|
||||
<SelectResourceStep
|
||||
columns={teamColumns}
|
||||
searchColumns={teamSearchColumns}
|
||||
sortColumns={teamSortColumns}
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={readTeams}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
|
||||
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { SearchColumns, SortColumns } from '@types';
|
||||
import PaginatedDataList from '../PaginatedDataList';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
import CheckboxListItem from '../CheckboxListItem';
|
||||
@ -23,7 +24,7 @@ class SelectResourceStep extends React.Component {
|
||||
this.qsConfig = getQSConfig('resource', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: props.sortedColumnKey,
|
||||
order_by: `${props.sortColumns.filter(col => col.key === 'name').length ? 'name' : 'username'}`
|
||||
});
|
||||
}
|
||||
|
||||
@ -50,6 +51,8 @@ class SelectResourceStep extends React.Component {
|
||||
const { data } = await onSearch(queryParams);
|
||||
const { count, results } = data;
|
||||
|
||||
debugger;
|
||||
|
||||
this.setState({
|
||||
resources: results,
|
||||
count,
|
||||
@ -69,7 +72,8 @@ class SelectResourceStep extends React.Component {
|
||||
const { isInitialized, isLoading, count, error, resources } = this.state;
|
||||
|
||||
const {
|
||||
columns,
|
||||
searchColumns,
|
||||
sortColumns,
|
||||
displayKey,
|
||||
onRowClick,
|
||||
selectedLabel,
|
||||
@ -99,8 +103,9 @@ class SelectResourceStep extends React.Component {
|
||||
items={resources}
|
||||
itemCount={count}
|
||||
qsConfig={this.qsConfig}
|
||||
toolbarColumns={columns}
|
||||
onRowClick={onRowClick}
|
||||
toolbarSearchColumns={searchColumns}
|
||||
toolbarSortColumns={sortColumns}
|
||||
renderItem={item => (
|
||||
<CheckboxListItem
|
||||
isSelected={selectedResourceRows.some(i => i.id === item.id)}
|
||||
@ -123,21 +128,22 @@ class SelectResourceStep extends React.Component {
|
||||
}
|
||||
|
||||
SelectResourceStep.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
searchColumns: SearchColumns,
|
||||
sortColumns: SortColumns,
|
||||
displayKey: PropTypes.string,
|
||||
onRowClick: PropTypes.func,
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
selectedLabel: PropTypes.string,
|
||||
selectedResourceRows: PropTypes.arrayOf(PropTypes.object),
|
||||
sortedColumnKey: PropTypes.string,
|
||||
};
|
||||
|
||||
SelectResourceStep.defaultProps = {
|
||||
searchColumns: null,
|
||||
sortColumns: null,
|
||||
displayKey: 'name',
|
||||
onRowClick: () => {},
|
||||
selectedLabel: null,
|
||||
selectedResourceRows: [],
|
||||
sortedColumnKey: 'name',
|
||||
};
|
||||
|
||||
export { SelectResourceStep as _SelectResourceStep };
|
||||
|
||||
@ -6,8 +6,19 @@ import { sleep } from '../../../testUtils/testUtils';
|
||||
import SelectResourceStep from './SelectResourceStep';
|
||||
|
||||
describe('<SelectResourceStep />', () => {
|
||||
const columns = [
|
||||
{ name: 'Username', key: 'username', isSortable: true, isSearchable: true },
|
||||
const searchColumns = [
|
||||
{
|
||||
name: 'Username',
|
||||
key: 'username',
|
||||
isDefault: true
|
||||
},
|
||||
];
|
||||
|
||||
const sortColumns = [
|
||||
{
|
||||
name: 'Username',
|
||||
key: 'username'
|
||||
},
|
||||
];
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
@ -15,11 +26,11 @@ describe('<SelectResourceStep />', () => {
|
||||
test('initially renders without crashing', () => {
|
||||
shallow(
|
||||
<SelectResourceStep
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={() => {}}
|
||||
sortedColumnKey="username"
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -36,11 +47,11 @@ describe('<SelectResourceStep />', () => {
|
||||
});
|
||||
mountWithContexts(
|
||||
<SelectResourceStep
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={handleSearch}
|
||||
sortedColumnKey="username"
|
||||
/>
|
||||
);
|
||||
expect(handleSearch).toHaveBeenCalledWith({
|
||||
@ -68,12 +79,12 @@ describe('<SelectResourceStep />', () => {
|
||||
});
|
||||
const wrapper = await mountWithContexts(
|
||||
<SelectResourceStep
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={handleSearch}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
sortedColumnKey="username"
|
||||
/>,
|
||||
{
|
||||
context: { router: { history, route: { location: history.location } } },
|
||||
@ -102,12 +113,12 @@ describe('<SelectResourceStep />', () => {
|
||||
};
|
||||
const wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={handleRowClick}
|
||||
onSearch={() => ({ data })}
|
||||
selectedResourceRows={[]}
|
||||
sortedColumnKey="username"
|
||||
/>
|
||||
);
|
||||
await sleep(0);
|
||||
|
||||
@ -8,14 +8,13 @@ import {
|
||||
ToolbarGroup as PFToolbarGroup,
|
||||
ToolbarItem,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import ExpandCollapse from '../ExpandCollapse';
|
||||
import Search from '../Search';
|
||||
import Sort from '../Sort';
|
||||
import VerticalSeparator from '../VerticalSeparator';
|
||||
|
||||
import { QSConfig } from '@types';
|
||||
import { SearchColumns, SortColumns, QSConfig } from '@types';
|
||||
|
||||
const AWXToolbar = styled.div`
|
||||
--awx-toolbar--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
||||
@ -86,7 +85,8 @@ const AdditionalControlsWrapper = styled.div`
|
||||
class DataListToolbar extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
columns,
|
||||
searchColumns,
|
||||
sortColumns,
|
||||
showSelectAll,
|
||||
isAllSelected,
|
||||
isCompact,
|
||||
@ -96,8 +96,6 @@ class DataListToolbar extends React.Component {
|
||||
onCompact,
|
||||
onExpand,
|
||||
onSelectAll,
|
||||
sortOrder,
|
||||
sortedColumnKey,
|
||||
additionalControls,
|
||||
i18n,
|
||||
qsConfig,
|
||||
@ -124,9 +122,8 @@ class DataListToolbar extends React.Component {
|
||||
<ToolbarItem css="flex-grow: 1;">
|
||||
<Search
|
||||
qsConfig={qsConfig}
|
||||
columns={columns}
|
||||
columns={searchColumns}
|
||||
onSearch={onSearch}
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
<VerticalSeparator />
|
||||
@ -134,10 +131,9 @@ class DataListToolbar extends React.Component {
|
||||
<ColumnRight fillWidth={fillWidth}>
|
||||
<ToolbarItem>
|
||||
<Sort
|
||||
columns={columns}
|
||||
qsConfig={qsConfig}
|
||||
columns={sortColumns}
|
||||
onSort={onSort}
|
||||
sortOrder={sortOrder}
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
{showExpandCollapse && (
|
||||
@ -165,7 +161,8 @@ class DataListToolbar extends React.Component {
|
||||
|
||||
DataListToolbar.propTypes = {
|
||||
qsConfig: QSConfig.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
searchColumns: SearchColumns.isRequired,
|
||||
sortColumns: SortColumns.isRequired,
|
||||
showSelectAll: PropTypes.bool,
|
||||
isAllSelected: PropTypes.bool,
|
||||
isCompact: PropTypes.bool,
|
||||
@ -175,8 +172,6 @@ DataListToolbar.propTypes = {
|
||||
onSearch: PropTypes.func,
|
||||
onSelectAll: PropTypes.func,
|
||||
onSort: PropTypes.func,
|
||||
sortOrder: PropTypes.string,
|
||||
sortedColumnKey: PropTypes.string,
|
||||
additionalControls: PropTypes.arrayOf(PropTypes.node),
|
||||
};
|
||||
|
||||
@ -190,8 +185,6 @@ DataListToolbar.defaultProps = {
|
||||
onSearch: null,
|
||||
onSelectAll: null,
|
||||
onSort: null,
|
||||
sortOrder: 'ascending',
|
||||
sortedColumnKey: 'name',
|
||||
additionalControls: [],
|
||||
};
|
||||
|
||||
|
||||
@ -23,11 +23,13 @@ describe('<DataListToolbar />', () => {
|
||||
const onSort = jest.fn();
|
||||
const onSelectAll = jest.fn();
|
||||
|
||||
test('it triggers the expected callbacks', () => {
|
||||
const columns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||
test('it triggers the expected callbacks', () => {
|
||||
const searchColumns = [
|
||||
{ name: 'Name', key: 'name', isDefault: true }
|
||||
];
|
||||
const sortColumns = [
|
||||
{ name: 'Name', key: 'name' }
|
||||
];
|
||||
|
||||
const search = 'button[aria-label="Search submit button"]';
|
||||
const searchTextInput = 'input[aria-label="Search text input"]';
|
||||
const selectAll = 'input[aria-label="Select all"]';
|
||||
@ -38,9 +40,8 @@ describe('<DataListToolbar />', () => {
|
||||
qsConfig={QS_CONFIG}
|
||||
isAllSelected={false}
|
||||
showExpandCollapse
|
||||
sortedColumnKey="name"
|
||||
sortOrder="ascending"
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
onSearch={onSearch}
|
||||
onSort={onSort}
|
||||
onSelectAll={onSelectAll}
|
||||
@ -74,19 +75,28 @@ describe('<DataListToolbar />', () => {
|
||||
const searchDropdownMenuItems =
|
||||
'DropdownMenu > ul[aria-labelledby="awx-search"]';
|
||||
|
||||
const multipleColumns = [
|
||||
{ name: 'Foo', key: 'foo', isSortable: true, isSearchable: true },
|
||||
{ name: 'Bar', key: 'bar', isSortable: true, isSearchable: true },
|
||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||
{ name: 'Baz', key: 'baz' },
|
||||
const NEW_QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
const searchColumns = [
|
||||
{ name: 'Foo', key: 'foo', isDefault: true },
|
||||
{ name: 'Bar', key: 'bar' }
|
||||
];
|
||||
const sortColumns = [
|
||||
{ name: 'Foo', key: 'foo' },
|
||||
{ name: 'Bar', key: 'bar' },
|
||||
{ name: 'Bakery', key: 'Bakery' }
|
||||
];
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="foo"
|
||||
sortOrder="ascending"
|
||||
columns={multipleColumns}
|
||||
qsConfig={NEW_QS_CONFIG}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
);
|
||||
@ -106,10 +116,9 @@ describe('<DataListToolbar />', () => {
|
||||
searchDropdownItems.at(0).simulate('click', mockedSortEvent);
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="foo"
|
||||
sortOrder="descending"
|
||||
columns={multipleColumns}
|
||||
qsConfig={NEW_QS_CONFIG}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
);
|
||||
@ -145,24 +154,57 @@ describe('<DataListToolbar />', () => {
|
||||
});
|
||||
|
||||
test('it displays correct sort icon', () => {
|
||||
const NUM_QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'id' },
|
||||
integerFields: ['page', 'page_size', 'id'],
|
||||
};
|
||||
|
||||
const NUM_DESC_QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: '-id' },
|
||||
integerFields: ['page', 'page_size', 'id'],
|
||||
};
|
||||
|
||||
const ALPH_QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size', 'id'],
|
||||
};
|
||||
|
||||
const ALPH_DESC_QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: '-name' },
|
||||
integerFields: ['page', 'page_size', 'id'],
|
||||
};
|
||||
|
||||
const downNumericIconSelector = 'SortNumericDownIcon';
|
||||
const upNumericIconSelector = 'SortNumericUpIcon';
|
||||
const downAlphaIconSelector = 'SortAlphaDownIcon';
|
||||
const upAlphaIconSelector = 'SortAlphaUpIcon';
|
||||
|
||||
const numericColumns = [
|
||||
{ name: 'ID', key: 'id', isSortable: true, isNumeric: true },
|
||||
{ name: 'ID', key: 'id', isDefault: true },
|
||||
];
|
||||
|
||||
const alphaColumns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isNumeric: false },
|
||||
{ name: 'Name', key: 'name', isDefault: true },
|
||||
];
|
||||
|
||||
const searchColumns = [
|
||||
{ name: 'Name', key: 'name', isDefault: true },
|
||||
{ name: 'ID', key: 'id' }
|
||||
];
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="id"
|
||||
sortOrder="descending"
|
||||
columns={numericColumns}
|
||||
qsConfig={NUM_DESC_QS_CONFIG}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={numericColumns}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -171,10 +213,9 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="id"
|
||||
sortOrder="ascending"
|
||||
columns={numericColumns}
|
||||
qsConfig={NUM_QS_CONFIG}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={numericColumns}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -183,10 +224,9 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
sortOrder="descending"
|
||||
columns={alphaColumns}
|
||||
qsConfig={ALPH_DESC_QS_CONFIG}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={alphaColumns}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -195,10 +235,9 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
sortOrder="ascending"
|
||||
columns={alphaColumns}
|
||||
qsConfig={ALPH_QS_CONFIG}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={alphaColumns}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -207,14 +246,18 @@ describe('<DataListToolbar />', () => {
|
||||
});
|
||||
|
||||
test('should render additionalControls', () => {
|
||||
const columns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||
const searchColumns = [
|
||||
{ name: 'Name', key: 'name', isDefault: true }
|
||||
];
|
||||
const sortColumns = [
|
||||
{ name: 'Name', key: 'name', isDefault: true }
|
||||
];
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
onSearch={onSearch}
|
||||
onSort={onSort}
|
||||
onSelectAll={onSelectAll}
|
||||
@ -232,18 +275,19 @@ describe('<DataListToolbar />', () => {
|
||||
});
|
||||
|
||||
test('it triggers the expected callbacks', () => {
|
||||
const columns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||
const searchColumns = [
|
||||
{ name: 'Name', key: 'name', isDefault: true }
|
||||
];
|
||||
const sortColumns = [
|
||||
{ name: 'Name', key: 'name' }
|
||||
];
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
isAllSelected
|
||||
showExpandCollapse
|
||||
sortedColumnKey="name"
|
||||
sortOrder="ascending"
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
onSearch={onSearch}
|
||||
onSort={onSort}
|
||||
onSelectAll={onSelectAll}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes, { arrayOf, shape, string, bool } from 'prop-types';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import DataListToolbar from '@components/DataListToolbar';
|
||||
import FilterTags from '@components/FilterTags';
|
||||
|
||||
@ -13,7 +12,7 @@ import {
|
||||
replaceParams,
|
||||
removeParams,
|
||||
} from '@util/qs';
|
||||
import { QSConfig } from '@types';
|
||||
import { QSConfig, SearchColumns, SortColumns } from '@types';
|
||||
|
||||
const EmptyStateControlsWrapper = styled.div`
|
||||
display: flex;
|
||||
@ -36,15 +35,6 @@ class ListHeader extends React.Component {
|
||||
this.handleRemoveAll = this.handleRemoveAll.bind(this);
|
||||
}
|
||||
|
||||
getSortOrder() {
|
||||
const { qsConfig, location } = this.props;
|
||||
const queryParams = parseQueryString(qsConfig, location.search);
|
||||
if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
|
||||
return [queryParams.order_by.substr(1), 'descending'];
|
||||
}
|
||||
return [queryParams.order_by, 'ascending'];
|
||||
}
|
||||
|
||||
handleSearch(key, value) {
|
||||
const { location, qsConfig } = this.props;
|
||||
const oldParams = parseQueryString(qsConfig, location.search);
|
||||
@ -83,12 +73,12 @@ class ListHeader extends React.Component {
|
||||
const {
|
||||
emptyStateControls,
|
||||
itemCount,
|
||||
columns,
|
||||
searchColumns,
|
||||
sortColumns,
|
||||
renderToolbar,
|
||||
qsConfig,
|
||||
location,
|
||||
} = this.props;
|
||||
const [orderBy, sortOrder] = this.getSortOrder();
|
||||
const params = parseQueryString(qsConfig, location.search);
|
||||
const isEmpty = itemCount === 0 && Object.keys(params).length === 0;
|
||||
return (
|
||||
@ -108,9 +98,8 @@ class ListHeader extends React.Component {
|
||||
) : (
|
||||
<Fragment>
|
||||
{renderToolbar({
|
||||
sortedColumnKey: orderBy,
|
||||
sortOrder,
|
||||
columns,
|
||||
searchColumns,
|
||||
sortColumns,
|
||||
onSearch: this.handleSearch,
|
||||
onSort: this.handleSort,
|
||||
qsConfig,
|
||||
@ -131,14 +120,8 @@ class ListHeader extends React.Component {
|
||||
ListHeader.propTypes = {
|
||||
itemCount: PropTypes.number.isRequired,
|
||||
qsConfig: QSConfig.isRequired,
|
||||
columns: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
key: string.isRequired,
|
||||
isSortable: bool,
|
||||
isSearchable: bool,
|
||||
})
|
||||
).isRequired,
|
||||
searchColumns: SearchColumns.isRequired,
|
||||
sortColumns: SortColumns.isRequired,
|
||||
renderToolbar: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import ListHeader from './ListHeader';
|
||||
describe('ListHeader', () => {
|
||||
const qsConfig = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
|
||||
integerFields: [],
|
||||
};
|
||||
const renderToolbarFn = jest.fn();
|
||||
@ -17,8 +17,11 @@ describe('ListHeader', () => {
|
||||
<ListHeader
|
||||
itemCount={50}
|
||||
qsConfig={qsConfig}
|
||||
columns={[
|
||||
{ name: 'foo', key: 'foo', isSearchable: true, isSortable: true },
|
||||
searchColumns={[
|
||||
{ name: 'foo', key: 'foo', isDefault: true},
|
||||
]}
|
||||
sortColumns={[
|
||||
{ name: 'foo', key: 'foo', isDefault: true},
|
||||
]}
|
||||
renderToolbar={renderToolbarFn}
|
||||
/>
|
||||
@ -35,26 +38,20 @@ describe('ListHeader', () => {
|
||||
<ListHeader
|
||||
itemCount={7}
|
||||
qsConfig={qsConfig}
|
||||
columns={[
|
||||
{ name: 'name', key: 'name', isSearchable: true, isSortable: true },
|
||||
searchColumns={[
|
||||
{ name: 'foo', key: 'foo', isDefault: true},
|
||||
]}
|
||||
sortColumns={[
|
||||
{ name: 'foo', key: 'foo', isDefault: true},
|
||||
]}
|
||||
/>,
|
||||
{ context: { router: { history } } }
|
||||
);
|
||||
|
||||
const toolbar = wrapper.find('DataListToolbar');
|
||||
expect(toolbar.prop('sortedColumnKey')).toEqual('name');
|
||||
expect(toolbar.prop('sortOrder')).toEqual('ascending');
|
||||
toolbar.prop('onSort')('name', 'descending');
|
||||
expect(history.location.search).toEqual('?item.order_by=-name');
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
|
||||
expect(toolbar.prop('sortedColumnKey')).toEqual('name');
|
||||
// TODO: this assertion required updating queryParams prop. Consider
|
||||
// fixing after #147 is done:
|
||||
// expect(toolbar.prop('sortOrder')).toEqual('descending');
|
||||
toolbar.prop('onSort')('name', 'ascending');
|
||||
toolbar.prop('onSort')('foo', 'descending');
|
||||
expect(history.location.search).toEqual('?item.order_by=-foo');
|
||||
toolbar.prop('onSort')('foo', 'ascending');
|
||||
// since order_by = name is the default, that should be strip out of the search
|
||||
expect(history.location.search).toEqual('');
|
||||
});
|
||||
|
||||
@ -64,26 +64,24 @@ function InstanceGroupsLookup(props) {
|
||||
value={state.selectedItems}
|
||||
options={instanceGroups}
|
||||
optionCount={count}
|
||||
columns={[
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
sortColumns={[{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name'
|
||||
}]}
|
||||
multiple={state.multiple}
|
||||
header={i18n._(t`Instance Groups`)}
|
||||
name="instanceGroups"
|
||||
|
||||
@ -68,21 +68,24 @@ function InventoryLookup({
|
||||
value={state.selectedItems}
|
||||
options={inventories}
|
||||
optionCount={count}
|
||||
columns={[
|
||||
{ name: i18n._(t`Name`), key: 'name', isSortable: true },
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: false,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
sortColumns={[{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name'
|
||||
}]}
|
||||
multiple={state.multiple}
|
||||
header={i18n._(t`Inventory`)}
|
||||
name="inventory"
|
||||
|
||||
@ -14,7 +14,7 @@ import SelectedList from '../../SelectedList';
|
||||
import PaginatedDataList from '../../PaginatedDataList';
|
||||
import CheckboxListItem from '../../CheckboxListItem';
|
||||
import DataListToolbar from '../../DataListToolbar';
|
||||
import { QSConfig } from '@types';
|
||||
import { QSConfig, SearchColumns, SortColumns } from '@types';
|
||||
|
||||
function OptionsList({
|
||||
value,
|
||||
@ -80,7 +80,8 @@ OptionsList.propTypes = {
|
||||
value: arrayOf(Item).isRequired,
|
||||
options: arrayOf(Item).isRequired,
|
||||
optionCount: number.isRequired,
|
||||
columns: arrayOf(shape({})),
|
||||
searchColumns: SearchColumns.isRequired,
|
||||
sortColumns: SortColumns.isRequired,
|
||||
multiple: bool,
|
||||
qsConfig: QSConfig.isRequired,
|
||||
selectItem: func.isRequired,
|
||||
@ -90,7 +91,6 @@ OptionsList.propTypes = {
|
||||
OptionsList.defaultProps = {
|
||||
multiple: false,
|
||||
renderItemChip: null,
|
||||
columns: [],
|
||||
};
|
||||
|
||||
export default withI18n()(OptionsList);
|
||||
|
||||
@ -18,12 +18,6 @@ const QS_CONFIG = getQSConfig('notification', {
|
||||
order_by: 'name',
|
||||
});
|
||||
|
||||
const COLUMNS = [
|
||||
{ key: 'name', name: 'Name', isSortable: true, isSearchable: true },
|
||||
{ key: 'modified', name: 'Modified', isSortable: true, isNumeric: true },
|
||||
{ key: 'created', name: 'Created', isSortable: true, isNumeric: true },
|
||||
];
|
||||
|
||||
class NotificationList extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -204,7 +198,35 @@ class NotificationList extends Component {
|
||||
itemCount={itemCount}
|
||||
pluralizedItemName={i18n._(t`Notifications`)}
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={COLUMNS}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
renderItem={notification => (
|
||||
<NotificationListItem
|
||||
key={notification.id}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes, { arrayOf, shape, string, bool } from 'prop-types';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DataList } from '@patternfly/react-core';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
@ -18,7 +18,7 @@ import {
|
||||
replaceParams,
|
||||
} from '@util/qs';
|
||||
|
||||
import { QSConfig } from '@types';
|
||||
import { QSConfig, SearchColumns, SortColumns } from '@types';
|
||||
|
||||
import PaginatedDataListItem from './PaginatedDataListItem';
|
||||
|
||||
@ -66,21 +66,29 @@ class PaginatedDataList extends React.Component {
|
||||
itemCount,
|
||||
qsConfig,
|
||||
renderItem,
|
||||
toolbarColumns,
|
||||
toolbarSearchColumns,
|
||||
toolbarSortColumns,
|
||||
pluralizedItemName,
|
||||
showPageSizeOptions,
|
||||
location,
|
||||
i18n,
|
||||
renderToolbar,
|
||||
} = this.props;
|
||||
const columns = toolbarColumns.length
|
||||
? toolbarColumns
|
||||
const searchColumns = toolbarSearchColumns.length
|
||||
? toolbarSearchColumns
|
||||
: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
];
|
||||
const sortColumns = toolbarSortColumns.length
|
||||
? toolbarSortColumns
|
||||
: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
];
|
||||
const queryParams = parseQueryString(qsConfig, location.search);
|
||||
@ -117,7 +125,8 @@ class PaginatedDataList extends React.Component {
|
||||
itemCount={itemCount}
|
||||
renderToolbar={renderToolbar}
|
||||
emptyStateControls={emptyStateControls}
|
||||
columns={columns}
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
qsConfig={qsConfig}
|
||||
/>
|
||||
{Content}
|
||||
@ -158,13 +167,8 @@ PaginatedDataList.propTypes = {
|
||||
pluralizedItemName: PropTypes.string,
|
||||
qsConfig: QSConfig.isRequired,
|
||||
renderItem: PropTypes.func,
|
||||
toolbarColumns: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
key: string.isRequired,
|
||||
isSortable: bool,
|
||||
})
|
||||
),
|
||||
toolbarSearchColumns: SearchColumns,
|
||||
toolbarSortColumns: SortColumns,
|
||||
showPageSizeOptions: PropTypes.bool,
|
||||
renderToolbar: PropTypes.func,
|
||||
hasContentLoading: PropTypes.bool,
|
||||
@ -175,7 +179,8 @@ PaginatedDataList.propTypes = {
|
||||
PaginatedDataList.defaultProps = {
|
||||
hasContentLoading: false,
|
||||
contentError: null,
|
||||
toolbarColumns: [],
|
||||
toolbarSearchColumns: [],
|
||||
toolbarSortColumns: [],
|
||||
pluralizedItemName: 'Items',
|
||||
showPageSizeOptions: true,
|
||||
renderItem: item => <PaginatedDataListItem key={item.id} item={item} />,
|
||||
|
||||
@ -162,24 +162,34 @@ class ResourceAccessList extends React.Component {
|
||||
itemCount={itemCount}
|
||||
pluralizedItemName="Roles"
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={[
|
||||
{
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
import { SearchIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { QSConfig } from '@types';
|
||||
import { QSConfig, SearchColumns } from '@types';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
@ -82,10 +82,11 @@ class Search extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { sortedColumnKey } = this.props;
|
||||
const { columns } = this.props;
|
||||
|
||||
this.state = {
|
||||
isSearchDropdownOpen: false,
|
||||
searchKey: sortedColumnKey,
|
||||
searchKey: columns.find(col => col.isDefault).key,
|
||||
searchValue: '',
|
||||
};
|
||||
|
||||
@ -142,7 +143,7 @@ class Search extends React.Component {
|
||||
);
|
||||
|
||||
const searchDropdownItems = columns
|
||||
.filter(({ key, isSearchable }) => isSearchable && key !== searchKey)
|
||||
.filter(({ key }) => key !== searchKey)
|
||||
.map(({ key, name }) => (
|
||||
<DropdownItem key={key} component="button">
|
||||
{name}
|
||||
@ -214,14 +215,12 @@ class Search extends React.Component {
|
||||
|
||||
Search.propTypes = {
|
||||
qsConfig: QSConfig.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSearch: PropTypes.func,
|
||||
sortedColumnKey: PropTypes.string,
|
||||
columns: SearchColumns.isRequired,
|
||||
onSearch: PropTypes.func
|
||||
};
|
||||
|
||||
Search.defaultProps = {
|
||||
onSearch: null,
|
||||
sortedColumnKey: 'name',
|
||||
};
|
||||
|
||||
export default withI18n()(Search);
|
||||
|
||||
@ -8,7 +8,7 @@ describe('<Search />', () => {
|
||||
const QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
defaulkntParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
@ -20,7 +20,7 @@ describe('<Search />', () => {
|
||||
|
||||
test('it triggers the expected callbacks', () => {
|
||||
const columns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||
{ name: 'Name', key: 'name', isDefault: true }
|
||||
];
|
||||
|
||||
const searchBtn = 'button[aria-label="Search submit button"]';
|
||||
@ -31,7 +31,6 @@ describe('<Search />', () => {
|
||||
search = mountWithContexts(
|
||||
<Search
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
@ -47,13 +46,12 @@ describe('<Search />', () => {
|
||||
|
||||
test('handleDropdownToggle properly updates state', async () => {
|
||||
const columns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||
{ name: 'Name', key: 'name', isDefault: true }
|
||||
];
|
||||
const onSearch = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<Search
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
@ -65,19 +63,13 @@ describe('<Search />', () => {
|
||||
|
||||
test('handleDropdownSelect properly updates state', async () => {
|
||||
const columns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||
{
|
||||
name: 'Description',
|
||||
key: 'description',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{ name: 'Name', key: 'name', isDefault: true },
|
||||
{ name: 'Description', key: 'description' },
|
||||
];
|
||||
const onSearch = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<Search
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
Button,
|
||||
@ -19,6 +20,11 @@ import {
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
parseQueryString
|
||||
} from '@util/qs';
|
||||
import { SortColumns, QSConfig } from '@types';
|
||||
|
||||
const Dropdown = styled(PFDropdown)`
|
||||
&&& {
|
||||
> button {
|
||||
@ -68,8 +74,31 @@ class Sort extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let sortKey;
|
||||
let sortOrder;
|
||||
let isNumeric;
|
||||
|
||||
const { qsConfig, location } = this.props;
|
||||
const queryParams = parseQueryString(qsConfig, location.search);
|
||||
if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
|
||||
sortKey = queryParams.order_by.substr(1);
|
||||
sortOrder = 'descending';
|
||||
} else if (queryParams.order_by) {
|
||||
sortKey = queryParams.order_by;
|
||||
sortOrder = 'ascending';
|
||||
}
|
||||
|
||||
if (qsConfig.integerFields.filter(field => field === sortKey).length) {
|
||||
isNumeric = true;
|
||||
} else {
|
||||
isNumeric = false;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
isSortDropdownOpen: false,
|
||||
sortKey,
|
||||
sortOrder,
|
||||
isNumeric
|
||||
};
|
||||
|
||||
this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
|
||||
@ -82,34 +111,44 @@ class Sort extends React.Component {
|
||||
}
|
||||
|
||||
handleDropdownSelect({ target }) {
|
||||
const { columns, onSort, sortOrder } = this.props;
|
||||
const { columns, onSort, qsConfig } = this.props;
|
||||
const { sortOrder } = this.state;
|
||||
const { innerText } = target;
|
||||
|
||||
const [{ key: searchKey }] = columns.filter(
|
||||
const [{ key: sortKey }] = columns.filter(
|
||||
({ name }) => name === innerText
|
||||
);
|
||||
|
||||
this.setState({ isSortDropdownOpen: false });
|
||||
onSort(searchKey, sortOrder);
|
||||
let isNumeric;
|
||||
|
||||
if (qsConfig.integerFields.filter(field => field === sortKey).length) {
|
||||
isNumeric = true;
|
||||
} else {
|
||||
isNumeric = false;
|
||||
}
|
||||
|
||||
this.setState({ isSortDropdownOpen: false, sortKey, isNumeric });
|
||||
onSort(sortKey, sortOrder);
|
||||
}
|
||||
|
||||
handleSort() {
|
||||
const { onSort, sortedColumnKey, sortOrder } = this.props;
|
||||
const { onSort } = this.props;
|
||||
const { sortKey, sortOrder } = this.state;
|
||||
const newSortOrder = sortOrder === 'ascending' ? 'descending' : 'ascending';
|
||||
|
||||
onSort(sortedColumnKey, newSortOrder);
|
||||
this.setState({ sortOrder: newSortOrder });
|
||||
onSort(sortKey, newSortOrder);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { up } = DropdownPosition;
|
||||
const { columns, sortedColumnKey, sortOrder, i18n } = this.props;
|
||||
const { isSortDropdownOpen } = this.state;
|
||||
const [{ name: sortedColumnName, isNumeric }] = columns.filter(
|
||||
({ key }) => key === sortedColumnKey
|
||||
const { columns, i18n } = this.props;
|
||||
const { isSortDropdownOpen, sortKey, sortOrder, isNumeric } = this.state;
|
||||
const [{ name: sortedColumnName }] = columns.filter(
|
||||
({ key }) => key === sortKey
|
||||
);
|
||||
|
||||
const sortDropdownItems = columns
|
||||
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
|
||||
.filter(({ key }) => key !== sortKey)
|
||||
.map(({ key, name }) => (
|
||||
<DropdownItem key={key} component="button">
|
||||
{name}
|
||||
@ -168,16 +207,13 @@ class Sort extends React.Component {
|
||||
}
|
||||
|
||||
Sort.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSort: PropTypes.func,
|
||||
sortOrder: PropTypes.string,
|
||||
sortedColumnKey: PropTypes.string,
|
||||
qsConfig: QSConfig.isRequired,
|
||||
columns: SortColumns.isRequired,
|
||||
onSort: PropTypes.func
|
||||
};
|
||||
|
||||
Sort.defaultProps = {
|
||||
onSort: null,
|
||||
sortOrder: 'ascending',
|
||||
sortedColumnKey: 'name',
|
||||
onSort: null
|
||||
};
|
||||
|
||||
export default withI18n()(Sort);
|
||||
export default withI18n()(withRouter(Sort));
|
||||
|
||||
@ -12,8 +12,17 @@ describe('<Sort />', () => {
|
||||
});
|
||||
|
||||
test('it triggers the expected callbacks', () => {
|
||||
const qsConfig = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||
{
|
||||
name: 'Name',
|
||||
key: 'name',
|
||||
},
|
||||
];
|
||||
|
||||
const sortBtn = 'button[aria-label="Sort"]';
|
||||
@ -22,8 +31,7 @@ describe('<Sort />', () => {
|
||||
|
||||
const wrapper = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="name"
|
||||
sortOrder="ascending"
|
||||
qsConfig={qsConfig}
|
||||
columns={columns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
@ -36,20 +44,33 @@ describe('<Sort />', () => {
|
||||
});
|
||||
|
||||
test('onSort properly passes back descending when ascending was passed as prop', () => {
|
||||
const multipleColumns = [
|
||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||
{ name: 'Baz', key: 'baz' },
|
||||
const qsConfig = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'Foo',
|
||||
key: 'foo',
|
||||
},
|
||||
{
|
||||
name: 'Bar',
|
||||
key: 'bar',
|
||||
},
|
||||
{
|
||||
name: 'Bakery',
|
||||
key: 'bakery',
|
||||
}
|
||||
];
|
||||
|
||||
const onSort = jest.fn();
|
||||
|
||||
const wrapper = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="foo"
|
||||
sortOrder="ascending"
|
||||
columns={multipleColumns}
|
||||
qsConfig={qsConfig}
|
||||
columns={columns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
).find('Sort');
|
||||
@ -60,20 +81,33 @@ describe('<Sort />', () => {
|
||||
});
|
||||
|
||||
test('onSort properly passes back ascending when descending was passed as prop', () => {
|
||||
const multipleColumns = [
|
||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||
{ name: 'Baz', key: 'baz' },
|
||||
const qsConfig = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: '-foo' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'Foo',
|
||||
key: 'foo',
|
||||
},
|
||||
{
|
||||
name: 'Bar',
|
||||
key: 'bar',
|
||||
},
|
||||
{
|
||||
name: 'Bakery',
|
||||
key: 'bakery',
|
||||
}
|
||||
];
|
||||
|
||||
const onSort = jest.fn();
|
||||
|
||||
const wrapper = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="foo"
|
||||
sortOrder="descending"
|
||||
columns={multipleColumns}
|
||||
qsConfig={qsConfig}
|
||||
columns={columns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
).find('Sort');
|
||||
@ -84,20 +118,33 @@ describe('<Sort />', () => {
|
||||
});
|
||||
|
||||
test('Changing dropdown correctly passes back new sort key', () => {
|
||||
const multipleColumns = [
|
||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||
{ name: 'Baz', key: 'baz' },
|
||||
const qsConfig = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'Foo',
|
||||
key: 'foo',
|
||||
},
|
||||
{
|
||||
name: 'Bar',
|
||||
key: 'bar',
|
||||
},
|
||||
{
|
||||
name: 'Bakery',
|
||||
key: 'bakery',
|
||||
}
|
||||
];
|
||||
|
||||
const onSort = jest.fn();
|
||||
|
||||
const wrapper = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="foo"
|
||||
sortOrder="ascending"
|
||||
columns={multipleColumns}
|
||||
qsConfig={qsConfig}
|
||||
columns={columns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
).find('Sort');
|
||||
@ -107,20 +154,33 @@ describe('<Sort />', () => {
|
||||
});
|
||||
|
||||
test('Opening dropdown correctly updates state', () => {
|
||||
const multipleColumns = [
|
||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||
{ name: 'Baz', key: 'baz' },
|
||||
const qsConfig = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'Foo',
|
||||
key: 'foo',
|
||||
},
|
||||
{
|
||||
name: 'Bar',
|
||||
key: 'bar',
|
||||
},
|
||||
{
|
||||
name: 'Bakery',
|
||||
key: 'bakery',
|
||||
}
|
||||
];
|
||||
|
||||
const onSort = jest.fn();
|
||||
|
||||
const wrapper = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="foo"
|
||||
sortOrder="ascending"
|
||||
columns={multipleColumns}
|
||||
qsConfig={qsConfig}
|
||||
columns={columns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
).find('Sort');
|
||||
@ -135,18 +195,38 @@ describe('<Sort />', () => {
|
||||
const downAlphaIconSelector = 'SortAlphaDownIcon';
|
||||
const upAlphaIconSelector = 'SortAlphaUpIcon';
|
||||
|
||||
const qsConfigNumDown = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: '-id' },
|
||||
integerFields: ['page', 'page_size', 'id'],
|
||||
};
|
||||
const qsConfigNumUp = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'id' },
|
||||
integerFields: ['page', 'page_size', 'id'],
|
||||
};
|
||||
const qsConfigAlphaDown = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: '-name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
const qsConfigAlphaUp = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
const numericColumns = [
|
||||
{ name: 'ID', key: 'id', isSortable: true, isNumeric: true },
|
||||
{ name: 'ID', key: 'id' },
|
||||
];
|
||||
const alphaColumns = [
|
||||
{ name: 'Name', key: 'name', isSortable: true, isNumeric: false },
|
||||
{ name: 'Name', key: 'name' },
|
||||
];
|
||||
const onSort = jest.fn();
|
||||
|
||||
sort = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="id"
|
||||
sortOrder="descending"
|
||||
qsConfig={qsConfigNumDown}
|
||||
columns={numericColumns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
@ -157,8 +237,7 @@ describe('<Sort />', () => {
|
||||
|
||||
sort = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="id"
|
||||
sortOrder="ascending"
|
||||
qsConfig={qsConfigNumUp}
|
||||
columns={numericColumns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
@ -169,8 +248,7 @@ describe('<Sort />', () => {
|
||||
|
||||
sort = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="name"
|
||||
sortOrder="descending"
|
||||
qsConfig={qsConfigAlphaDown}
|
||||
columns={alphaColumns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
@ -181,8 +259,7 @@ describe('<Sort />', () => {
|
||||
|
||||
sort = mountWithContexts(
|
||||
<Sort
|
||||
sortedColumnKey="name"
|
||||
sortOrder="ascending"
|
||||
qsConfig={qsConfigAlphaUp}
|
||||
columns={alphaColumns}
|
||||
onSort={onSort}
|
||||
/>
|
||||
|
||||
@ -190,24 +190,33 @@ class HostsList extends Component {
|
||||
pluralizedItemName={i18n._(t`Hosts`)}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={this.handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -174,24 +174,33 @@ class InventoriesList extends Component {
|
||||
pluralizedItemName={i18n._(t`Inventories`)}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={this.handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -163,18 +163,25 @@ class JobList extends Component {
|
||||
pluralizedItemName="Jobs"
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={this.handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Finished`),
|
||||
key: 'finished',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Finished`),
|
||||
key: 'finished',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -119,24 +119,33 @@ function OrganizationsList({ i18n }) {
|
||||
pluralizedItemName="Organizations"
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -156,24 +156,33 @@ class ProjectsList extends Component {
|
||||
pluralizedItemName={i18n._(t`Projects`)}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={this.handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -154,24 +154,33 @@ class TeamsList extends Component {
|
||||
pluralizedItemName={i18n._(t`Teams`)}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={this.handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -214,24 +214,33 @@ class TemplatesList extends Component {
|
||||
pluralizedItemName={i18n._(t`Templates`)}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={this.handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified`),
|
||||
key: 'modified',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -154,24 +154,34 @@ class UsersList extends Component {
|
||||
pluralizedItemName="Users"
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={this.handleSelect}
|
||||
toolbarColumns={[
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
isSortable: true,
|
||||
isSearchable: true,
|
||||
},
|
||||
]}
|
||||
renderToolbar={props => (
|
||||
|
||||
@ -249,3 +249,18 @@ export const Group = shape({
|
||||
inventory: number,
|
||||
variables: string,
|
||||
});
|
||||
|
||||
export const SearchColumns = arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
key: string.isRequired,
|
||||
isDefault: bool,
|
||||
})
|
||||
);
|
||||
|
||||
export const SortColumns = arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
key: string.isRequired,
|
||||
})
|
||||
);
|
||||
|
||||
@ -14,6 +14,10 @@ export function getQSConfig(
|
||||
if (!namespace) {
|
||||
throw new Error('a QS namespace is required');
|
||||
}
|
||||
// if order_by isn't passed, default to name
|
||||
if (!Object.keys(defaultParams).filter(key => key === 'order_by').length) {
|
||||
defaultParams.order_by = 'name';
|
||||
}
|
||||
return {
|
||||
namespace,
|
||||
defaultParams,
|
||||
|
||||
@ -121,6 +121,18 @@ describe('qs (qs.js)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should set order_by in defaultParams if it is not passed', () => {
|
||||
expect(getQSConfig('organization', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
})).toEqual({
|
||||
namespace: 'organization',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
dateFields: ['modified', 'created'],
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw if no namespace given', () => {
|
||||
expect(() => getQSConfig()).toThrow();
|
||||
});
|
||||
@ -132,7 +144,7 @@ describe('qs (qs.js)', () => {
|
||||
};
|
||||
expect(getQSConfig('inventory', defaults)).toEqual({
|
||||
namespace: 'inventory',
|
||||
defaultParams: { page: 1, page_size: 15 },
|
||||
defaultParams: { page: 1, page_size: 15, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
dateFields: ['modified', 'created'],
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user