more ui_next search pr feedback:

- updae .filter().length calls to .find()
- fix ProjectList errors
This commit is contained in:
John Mitchell 2020-01-09 13:55:19 -05:00
parent 3cdf274bdb
commit 1e344bdf8a
37 changed files with 330 additions and 417 deletions

View File

@ -146,7 +146,7 @@ class AddResourceRole extends React.Component {
{
name: i18n._(t`Username`),
key: 'username',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`First Name`),
@ -155,7 +155,7 @@ class AddResourceRole extends React.Component {
{
name: i18n._(t`Last Name`),
key: 'last_name',
}
},
];
const userSortColumns = [
@ -170,14 +170,14 @@ class AddResourceRole extends React.Component {
{
name: i18n._(t`Last Name`),
key: 'last_name',
}
},
];
const teamSearchColumns = [
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),

View File

@ -24,7 +24,11 @@ class SelectResourceStep extends React.Component {
this.qsConfig = getQSConfig('resource', {
page: 1,
page_size: 5,
order_by: `${props.sortColumns.filter(col => col.key === 'name').length ? 'name' : 'username'}`
order_by: `${
props.sortColumns.filter(col => col.key === 'name').length
? 'name'
: 'username'
}`,
});
}

View File

@ -10,14 +10,14 @@ describe('<SelectResourceStep />', () => {
{
name: 'Username',
key: 'username',
isDefault: true
isDefault: true,
},
];
const sortColumns = [
{
name: 'Username',
key: 'username'
key: 'username',
},
];
afterEach(() => {

View File

@ -2,12 +2,16 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Checkbox,
} from '@patternfly/react-core';
import { Checkbox } from '@patternfly/react-core';
import styled from 'styled-components';
import { SearchIcon } from '@patternfly/react-icons';
import { DataToolbar, DataToolbarContent, DataToolbarGroup, DataToolbarToggleGroup, DataToolbarItem } from '@patternfly/react-core/dist/umd/experimental';
import {
DataToolbar,
DataToolbarContent,
DataToolbarGroup,
DataToolbarToggleGroup,
DataToolbarItem,
} from '@patternfly/react-core/dist/umd/experimental';
import ExpandCollapse from '../ExpandCollapse';
import Search from '../Search';
import Sort from '../Sort';
@ -60,7 +64,8 @@ class DataListToolbar extends React.Component {
const showExpandCollapse = onCompact && onExpand;
return (
<DataToolbar id={`${qsConfig.namespace}-list-toolbar`}
<DataToolbar
id={`${qsConfig.namespace}-list-toolbar`}
clearAllFilters={clearAllFilters}
collapseListedFiltersBreakpoint="xl"
>
@ -89,11 +94,7 @@ class DataListToolbar extends React.Component {
/>
</DataToolbarItem>
<DataToolbarItem>
<Sort
qsConfig={qsConfig}
columns={sortColumns}
onSort={onSort}
/>
<Sort qsConfig={qsConfig} columns={sortColumns} onSort={onSort} />
</DataToolbarItem>
</DataToolbarToggleGroup>
<DataToolbarGroup>

View File

@ -25,12 +25,8 @@ describe('<DataListToolbar />', () => {
const onSelectAll = jest.fn();
test('it triggers the expected callbacks', () => {
const searchColumns = [
{ name: 'Name', key: 'name', isDefault: true }
];
const sortColumns = [
{ name: 'Name', key: 'name' }
];
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"]';
@ -86,12 +82,12 @@ describe('<DataListToolbar />', () => {
const searchColumns = [
{ name: 'Foo', key: 'foo', isDefault: true },
{ name: 'Bar', key: 'bar' }
{ name: 'Bar', key: 'bar' },
];
const sortColumns = [
{ name: 'Foo', key: 'foo' },
{ name: 'Bar', key: 'bar' },
{ name: 'Bakery', key: 'Bakery' }
{ name: 'Bakery', key: 'Bakery' },
];
toolbar = mountWithContexts(
@ -189,17 +185,13 @@ describe('<DataListToolbar />', () => {
const downAlphaIconSelector = 'SortAlphaDownIcon';
const upAlphaIconSelector = 'SortAlphaUpIcon';
const numericColumns = [
{ name: 'ID', key: 'id' },
];
const numericColumns = [{ name: 'ID', key: 'id' }];
const alphaColumns = [
{ name: 'Name', key: 'name' },
];
const alphaColumns = [{ name: 'Name', key: 'name' }];
const searchColumns = [
{ name: 'Name', key: 'name', isDefault: true },
{ name: 'ID', key: 'id' }
{ name: 'ID', key: 'id' },
];
toolbar = mountWithContexts(
@ -248,12 +240,8 @@ describe('<DataListToolbar />', () => {
});
test('should render additionalControls', () => {
const searchColumns = [
{ name: 'Name', key: 'name', isDefault: true }
];
const sortColumns = [
{ name: 'Name', key: 'name' }
];
const searchColumns = [{ name: 'Name', key: 'name', isDefault: true }];
const sortColumns = [{ name: 'Name', key: 'name' }];
toolbar = mountWithContexts(
<DataListToolbar
@ -278,12 +266,8 @@ describe('<DataListToolbar />', () => {
});
test('it triggers the expected callbacks', () => {
const searchColumns = [
{ name: 'Name', key: 'name', isDefault: true }
];
const sortColumns = [
{ name: 'Name', key: 'name' }
];
const searchColumns = [{ name: 'Name', key: 'name', isDefault: true }];
const sortColumns = [{ name: 'Name', key: 'name' }];
toolbar = mountWithContexts(
<DataListToolbar
qsConfig={QS_CONFIG}

View File

@ -2,7 +2,10 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import styled from 'styled-components';
import { DataToolbar, DataToolbarContent } from '@patternfly/react-core/dist/umd/experimental';
import {
DataToolbar,
DataToolbarContent,
} from '@patternfly/react-core/dist/umd/experimental';
import DataListToolbar from '@components/DataListToolbar';
import {
@ -40,7 +43,7 @@ class ListHeader extends React.Component {
const { location, qsConfig } = this.props;
let params = parseQueryString(qsConfig, location.search);
params = mergeParams(params, { [key]: value });
params = replaceParams(params, { 'page' : 1 })
params = replaceParams(params, { page: 1 });
this.pushHistoryState(params);
}
@ -54,7 +57,9 @@ class ListHeader extends React.Component {
const { location, qsConfig } = this.props;
let oldParams = parseQueryString(qsConfig, location.search);
if (parseInt(value, 10)) {
oldParams = removeParams(qsConfig, oldParams, { [key]: parseInt(value, 10) });
oldParams = removeParams(qsConfig, oldParams, {
[key]: parseInt(value, 10),
});
}
this.pushHistoryState(removeParams(qsConfig, oldParams, { [key]: value }));
}
@ -96,7 +101,8 @@ class ListHeader extends React.Component {
return (
<Fragment>
{isEmpty ? (
<DataToolbar id={`${qsConfig.namespace}-list-toolbar`}
<DataToolbar
id={`${qsConfig.namespace}-list-toolbar`}
clearAllFilters={this.handleRemoveAll}
collapseListedFiltersBreakpoint="md"
>

View File

@ -16,12 +16,8 @@ describe('ListHeader', () => {
<ListHeader
itemCount={50}
qsConfig={qsConfig}
searchColumns={[
{ name: 'foo', key: 'foo', isDefault: true},
]}
sortColumns={[
{ name: 'foo', key: 'foo'},
]}
searchColumns={[{ name: 'foo', key: 'foo', isDefault: true }]}
sortColumns={[{ name: 'foo', key: 'foo' }]}
renderToolbar={renderToolbarFn}
/>
);
@ -37,12 +33,8 @@ describe('ListHeader', () => {
<ListHeader
itemCount={7}
qsConfig={qsConfig}
searchColumns={[
{ name: 'foo', key: 'foo', isDefault: true},
]}
sortColumns={[
{ name: 'foo', key: 'foo'},
]}
searchColumns={[{ name: 'foo', key: 'foo', isDefault: true }]}
sortColumns={[{ name: 'foo', key: 'foo' }]}
/>,
{ context: { router: { history } } }
);

View File

@ -27,7 +27,7 @@ function CredentialLookup({
credentialTypeId,
value,
history,
i18n
i18n,
}) {
const [credentials, setCredentials] = useState([]);
const [count, setCount] = useState(0);
@ -79,7 +79,7 @@ function CredentialLookup({
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),
@ -88,12 +88,14 @@ function CredentialLookup({
{
name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username',
}
},
]}
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
sortColumns={[{
name: i18n._(t`Name`),
key: 'name'
}]}
readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}

View File

@ -68,11 +68,11 @@ function InstanceGroupsLookup(props) {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Credential Name`),
key: 'credential__name'
key: 'credential__name',
},
{
name: i18n._(t`Created By (Username)`),
@ -83,10 +83,12 @@ function InstanceGroupsLookup(props) {
key: 'modified_by__username',
},
]}
sortColumns={[{
name: i18n._(t`Name`),
key: 'name'
}]}
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
multiple={state.multiple}
header={i18n._(t`Instance Groups`)}
name="instanceGroups"

View File

@ -72,7 +72,7 @@ function InventoryLookup({
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),

View File

@ -126,7 +126,7 @@ function MultiCredentialsLookup(props) {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),
@ -137,10 +137,12 @@ function MultiCredentialsLookup(props) {
key: 'modified_by__username',
},
]}
sortColumns={[{
name: i18n._(t`Name`),
key: 'name'
}]}
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
multiple={isMultiple}
header={i18n._(t`Credentials`)}
name="credentials"

View File

@ -74,7 +74,7 @@ function OrganizationLookup({
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),
@ -85,10 +85,12 @@ function OrganizationLookup({
key: 'modified_by__username',
},
]}
sortColumns={[{
name: i18n._(t`Name`),
key: 'name'
}]}
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}

View File

@ -74,32 +74,17 @@ function ProjectLookup({
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Type`),
options: [
[
``,
i18n._(t`Manual`)
],
[
`git`,
i18n._(t`Git`)
],
[
`hg`,
i18n._(t`Mercurial`)
],
[
`svn`,
i18n._(t`Subversion`)
],
[
`insights`,
i18n._(t`Red Hat Insights`)
]
]
[``, i18n._(t`Manual`)],
[`git`, i18n._(t`Git`)],
[`hg`, i18n._(t`Mercurial`)],
[`svn`, i18n._(t`Subversion`)],
[`insights`, i18n._(t`Red Hat Insights`)],
],
},
{
name: i18n._(t`SCM URL`),
@ -118,7 +103,7 @@ function ProjectLookup({
{
name: i18n._(t`Name`),
key: 'name',
}
},
]}
options={projects}
optionCount={count}

View File

@ -101,7 +101,7 @@ OptionsList.defaultProps = {
multiple: false,
renderItemChip: null,
searchColumns: [],
sortColumns: []
sortColumns: [],
};
export default withI18n()(OptionsList);

View File

@ -17,7 +17,7 @@ describe('<OptionsList />', () => {
value={[]}
options={options}
optionCount={3}
searchColumns={[{name: 'Foo', key: 'foo', isDefault: true}]}
searchColumns={[{ name: 'Foo', key: 'foo', isDefault: true }]}
sortColumns={[{ name: 'Foo', key: 'foo' }]}
qsConfig={qsConfig}
selectItem={() => {}}
@ -40,7 +40,7 @@ describe('<OptionsList />', () => {
value={[options[1]]}
options={options}
optionCount={3}
searchColumns={[{name: 'Foo', key: 'foo', isDefault: true}]}
searchColumns={[{ name: 'Foo', key: 'foo', isDefault: true }]}
sortColumns={[{ name: 'Foo', key: 'foo' }]}
qsConfig={qsConfig}
selectItem={() => {}}

View File

@ -202,7 +202,7 @@ class NotificationList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Type`),
@ -218,7 +218,7 @@ class NotificationList extends Component {
['slack', i18n._(t`Slack`)],
['twilio', i18n._(t`Twilio`)],
['webhook', i18n._(t`Webhook`)],
]
],
},
{
name: i18n._(t`Created By (Username)`),

View File

@ -80,7 +80,7 @@ class PaginatedDataList extends React.Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
];
const sortColumns = toolbarSortColumns.length

View File

@ -166,7 +166,7 @@ class ResourceAccessList extends React.Component {
{
name: i18n._(t`Username`),
key: 'username',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`First Name`),

View File

@ -19,7 +19,7 @@ import {
import {
DataToolbarGroup,
DataToolbarItem,
DataToolbarFilter
DataToolbarFilter,
} from '@patternfly/react-core/dist/umd/experimental';
import { SearchIcon } from '@patternfly/react-icons';
import { parseQueryString } from '@util/qs';
@ -44,7 +44,7 @@ class Search extends React.Component {
isSearchDropdownOpen: false,
searchKey: columns.find(col => col.isDefault).key,
searchValue: '',
isFilterDropdownOpen: false
isFilterDropdownOpen: false,
};
this.handleSearchInputChange = this.handleSearchInputChange.bind(this);
@ -52,8 +52,12 @@ class Search extends React.Component {
this.handleDropdownSelect = this.handleDropdownSelect.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.handleTextKeyDown = this.handleTextKeyDown.bind(this);
this.handleFilterDropdownToggle = this.handleFilterDropdownToggle.bind(this);
this.handleFilterDropdownSelect = this.handleFilterDropdownSelect.bind(this);
this.handleFilterDropdownToggle = this.handleFilterDropdownToggle.bind(
this
);
this.handleFilterDropdownSelect = this.handleFilterDropdownSelect.bind(
this
);
this.handleFilterBooleanSelect = this.handleFilterBooleanSelect.bind(this);
}
@ -77,8 +81,8 @@ class Search extends React.Component {
const { onSearch, qsConfig } = this.props;
const isNonStringField =
qsConfig.integerFields.filter(field => field === searchKey).length ||
qsConfig.dateFields.filter(field => field === searchKey).length;
qsConfig.integerFields.find(field => field === searchKey) ||
qsConfig.dateFields.find(field => field === searchKey);
const actualSearchKey = isNonStringField
? searchKey
@ -121,7 +125,12 @@ class Search extends React.Component {
render() {
const { up } = DropdownPosition;
const { columns, i18n, onRemove, qsConfig, location } = this.props;
const { isSearchDropdownOpen, searchKey, searchValue, isFilterDropdownOpen } = this.state;
const {
isSearchDropdownOpen,
searchKey,
searchValue,
isFilterDropdownOpen,
} = this.state;
const { name: searchColumnName } = columns.find(
({ key }) => key === searchKey
);
@ -143,8 +152,8 @@ class Search extends React.Component {
const queryParams = parseQueryString(qsConfig, location.search);
const queryParamsByKey = {};
columns.forEach(({name, key}) => {
queryParamsByKey[key] = {key, label: name, chips: []};
columns.forEach(({ name, key }) => {
queryParamsByKey[key] = { key, label: name, chips: [] };
});
const nonDefaultParams = filterDefaultParams(
Object.keys(queryParams || {}),
@ -152,12 +161,13 @@ class Search extends React.Component {
);
nonDefaultParams.forEach(key => {
const columnKey = key
.replace('__icontains', '')
.replace('or__', '');
const label = columns
.filter(({key: keyToCheck}) => columnKey === keyToCheck).length ? columns
.filter(({key: keyToCheck}) => columnKey === keyToCheck)[0].name : columnKey;
const columnKey = key.replace('__icontains', '').replace('or__', '');
const label = columns.filter(
({ key: keyToCheck }) => columnKey === keyToCheck
).length
? columns.filter(({ key: keyToCheck }) => columnKey === keyToCheck)[0]
.name
: columnKey;
queryParamsByKey[columnKey] = { key, label, chips: [] };
@ -171,7 +181,7 @@ class Search extends React.Component {
});
return queryParamsByKey;
}
};
const chipsByKey = getChipsByKey();
@ -179,7 +189,7 @@ class Search extends React.Component {
<DataToolbarGroup variant="filter-group">
<DataToolbarItem>
{searchDropdownItems.length > 0 ? (
<Dropdown
<Dropdown
onToggle={this.handleDropdownToggle}
onSelect={this.handleDropdownSelect}
direction={up}
@ -195,68 +205,90 @@ class Search extends React.Component {
isOpen={isSearchDropdownOpen}
dropdownItems={searchDropdownItems}
style={{ width: '100%' }}
/>) : (<NoOptionDropdown>{searchColumnName}</NoOptionDropdown>)}
</DataToolbarItem>
{columns.map(({ key, name, options, isBoolean }) => (<DataToolbarFilter
chips={chipsByKey[key] ? chipsByKey[key].chips : []}
deleteChip={(unusedKey, val) => { onRemove(chipsByKey[key].key, val) }}
categoryName={chipsByKey[key] ? chipsByKey[key].label : key}
key={key}
showToolbarItem={searchKey === key}
>
{options && (<Select
variant={SelectVariant.checkbox}
aria-label={name}
onToggle={this.handleFilterDropdownToggle}
onSelect={(event, selection) => this.handleFilterDropdownSelect(key, event, selection)}
selections={chipsByKey[key].chips}
isExpanded={isFilterDropdownOpen}
placeholderText={`Filter by ${name.toLowerCase()}`}
>
{options.map(([optionKey]) => (
<Fragment key={optionKey}>
{ /* TODO: update value to being object
{ actualValue: optionKey, toString: () => label }
currently a pf bug that makes the checked logic
not work with object-based values */ }
<SelectOption key={optionKey} value={optionKey} />
</Fragment>
))}
</Select>) || isBoolean && (
<Select
aria-label={name}
onToggle={this.handleFilterDropdownToggle}
onSelect={(event, selection) => this.handleFilterBooleanSelect(key, selection)}
selections={chipsByKey[key].chips[0]}
isExpanded={isFilterDropdownOpen}
placeholderText={`Filter by ${name.toLowerCase()}`}
>
{ /* TODO: update value to being object
{ actualValue: optionKey, toString: () => label }
currently a pf bug that makes the checked logic
not work with object-based values */ }
<SelectOption key="true" value="true" />
<SelectOption key="false" value="false" />
</Select>
) || (<InputGroup>
{/* TODO: add support for dates:
qsConfig.dateFields.filter(field => field === key).length && "date" */}
<TextInput
type={qsConfig.integerFields.filter(field => field === key).length && "number" || "search"}
aria-label={i18n._(t`Search text input`)}
value={searchValue}
onChange={this.handleSearchInputChange}
onKeyDown={this.handleTextKeyDown}
/>
<Button
variant={ButtonVariant.control}
aria-label={i18n._(t`Search submit button`)}
onClick={this.handleSearch}
>
<SearchIcon />
</Button>
</InputGroup>)}
</DataToolbarFilter>))}
) : (
<NoOptionDropdown>{searchColumnName}</NoOptionDropdown>
)}
</DataToolbarItem>
{columns.map(({ key, name, options, isBoolean }) => (
<DataToolbarFilter
chips={chipsByKey[key] ? chipsByKey[key].chips : []}
deleteChip={(unusedKey, val) => {
onRemove(chipsByKey[key].key, val);
}}
categoryName={chipsByKey[key] ? chipsByKey[key].label : key}
key={key}
showToolbarItem={searchKey === key}
>
{(options && (
<Select
variant={SelectVariant.checkbox}
aria-label={name}
onToggle={this.handleFilterDropdownToggle}
onSelect={(event, selection) =>
this.handleFilterDropdownSelect(key, event, selection)
}
selections={chipsByKey[key].chips}
isExpanded={isFilterDropdownOpen}
placeholderText={`Filter by ${name.toLowerCase()}`}
>
{options.map(([optionKey]) => (
<Fragment key={optionKey}>
{/* TODO: update value to being object
{ actualValue: optionKey, toString: () => label }
currently a pf bug that makes the checked logic
not work with object-based values */}
<SelectOption key={optionKey} value={optionKey} />
</Fragment>
))}
</Select>
)) ||
(isBoolean && (
<Select
aria-label={name}
onToggle={this.handleFilterDropdownToggle}
onSelect={(event, selection) =>
this.handleFilterBooleanSelect(key, selection)
}
selections={chipsByKey[key].chips[0]}
isExpanded={isFilterDropdownOpen}
placeholderText={`Filter by ${name.toLowerCase()}`}
>
{/* TODO: update value to being object
{ actualValue: optionKey, toString: () => label }
currently a pf bug that makes the checked logic
not work with object-based values */}
<SelectOption key="true" value="true" />
<SelectOption key="false" value="false" />
</Select>
)) || (
<InputGroup>
{/* TODO: add support for dates:
qsConfig.dateFields.filter(field => field === key).length && "date" */}
<TextInput
type={
(qsConfig.integerFields.find(
field => field === searchKey
) &&
'number') ||
'search'
}
aria-label={i18n._(t`Search text input`)}
value={searchValue}
onChange={this.handleSearchInputChange}
onKeyDown={this.handleTextKeyDown}
/>
<Button
variant={ButtonVariant.control}
aria-label={i18n._(t`Search submit button`)}
onClick={this.handleSearch}
>
<SearchIcon />
</Button>
</InputGroup>
)}
</DataToolbarFilter>
))}
</DataToolbarGroup>
);
}
@ -266,12 +298,12 @@ Search.propTypes = {
qsConfig: QSConfig.isRequired,
columns: SearchColumns.isRequired,
onSearch: PropTypes.func,
onRemove: PropTypes.func
onRemove: PropTypes.func,
};
Search.defaultProps = {
onSearch: null,
onRemove: null
onRemove: null,
};
export default withI18n()(withRouter(Search));

View File

@ -1,5 +1,8 @@
import React from 'react';
import { DataToolbar, DataToolbarContent } from '@patternfly/react-core/dist/umd/experimental';
import {
DataToolbar,
DataToolbarContent,
} from '@patternfly/react-core/dist/umd/experimental';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import Search from './Search';
@ -20,9 +23,7 @@ describe('<Search />', () => {
});
test('it triggers the expected callbacks', () => {
const columns = [
{ name: 'Name', key: 'name', isDefault: true }
];
const columns = [{ name: 'Name', key: 'name', isDefault: true }];
const searchBtn = 'button[aria-label="Search submit button"]';
const searchTextInput = 'input[aria-label="Search text input"]';
@ -30,16 +31,13 @@ describe('<Search />', () => {
const onSearch = jest.fn();
search = mountWithContexts(
<DataToolbar id={`${QS_CONFIG.namespace}-list-toolbar`}
<DataToolbar
id={`${QS_CONFIG.namespace}-list-toolbar`}
clearAllFilters={() => {}}
collapseListedFiltersBreakpoint="md"
>
<DataToolbarContent>
<Search
qsConfig={QS_CONFIG}
columns={columns}
onSearch={onSearch}
/>
<Search qsConfig={QS_CONFIG} columns={columns} onSearch={onSearch} />
</DataToolbarContent>
</DataToolbar>
);
@ -53,21 +51,16 @@ describe('<Search />', () => {
});
test('handleDropdownToggle properly updates state', async () => {
const columns = [
{ name: 'Name', key: 'name', isDefault: true }
];
const columns = [{ name: 'Name', key: 'name', isDefault: true }];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
<DataToolbar id={`${QS_CONFIG.namespace}-list-toolbar`}
<DataToolbar
id={`${QS_CONFIG.namespace}-list-toolbar`}
clearAllFilters={() => {}}
collapseListedFiltersBreakpoint="md"
>
<DataToolbarContent>
<Search
qsConfig={QS_CONFIG}
columns={columns}
onSearch={onSearch}
/>
<Search qsConfig={QS_CONFIG} columns={columns} onSearch={onSearch} />
</DataToolbarContent>
</DataToolbar>
).find('Search');
@ -83,16 +76,13 @@ describe('<Search />', () => {
];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
<DataToolbar id={`${QS_CONFIG.namespace}-list-toolbar`}
<DataToolbar
id={`${QS_CONFIG.namespace}-list-toolbar`}
clearAllFilters={() => {}}
collapseListedFiltersBreakpoint="md"
>
<DataToolbarContent>
<Search
qsConfig={QS_CONFIG}
columns={columns}
onSearch={onSearch}
/>
<Search qsConfig={QS_CONFIG} columns={columns} onSearch={onSearch} />
</DataToolbarContent>
</DataToolbar>
).find('Search');

View File

@ -19,9 +19,7 @@ import {
SortNumericUpIcon,
} from '@patternfly/react-icons';
import {
parseQueryString
} from '@util/qs';
import { parseQueryString } from '@util/qs';
import { SortColumns, QSConfig } from '@types';
import styled from 'styled-components';
@ -51,7 +49,7 @@ class Sort extends React.Component {
sortOrder = 'ascending';
}
if (qsConfig.integerFields.filter(field => field === sortKey).length) {
if (qsConfig.integerFields.find(field => field === sortKey)) {
isNumeric = true;
} else {
isNumeric = false;
@ -61,7 +59,7 @@ class Sort extends React.Component {
isSortDropdownOpen: false,
sortKey,
sortOrder,
isNumeric
isNumeric,
};
this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
@ -78,13 +76,11 @@ class Sort extends React.Component {
const { sortOrder } = this.state;
const { innerText } = target;
const [{ key: sortKey }] = columns.filter(
({ name }) => name === innerText
);
const [{ key: sortKey }] = columns.filter(({ name }) => name === innerText);
let isNumeric;
if (qsConfig.integerFields.filter(field => field === sortKey).length) {
if (qsConfig.integerFields.find(field => field === sortKey)) {
isNumeric = true;
} else {
isNumeric = false;
@ -131,23 +127,23 @@ class Sort extends React.Component {
<Fragment>
{sortedColumnName && (
<InputGroup>
{sortDropdownItems.length > 0 && (<Dropdown
onToggle={this.handleDropdownToggle}
onSelect={this.handleDropdownSelect}
direction={up}
isOpen={isSortDropdownOpen}
toggle={
<DropdownToggle
id="awx-sort"
onToggle={this.handleDropdownToggle}
>
{sortedColumnName}
</DropdownToggle>
}
dropdownItems={sortDropdownItems}
/>) || (
<NoOptionDropdown>{sortedColumnName}</NoOptionDropdown>
)}
{(sortDropdownItems.length > 0 && (
<Dropdown
onToggle={this.handleDropdownToggle}
onSelect={this.handleDropdownSelect}
direction={up}
isOpen={isSortDropdownOpen}
toggle={
<DropdownToggle
id="awx-sort"
onToggle={this.handleDropdownToggle}
>
{sortedColumnName}
</DropdownToggle>
}
dropdownItems={sortDropdownItems}
/>
)) || <NoOptionDropdown>{sortedColumnName}</NoOptionDropdown>}
<Button
variant={ButtonVariant.control}
aria-label={i18n._(t`Sort`)}
@ -165,11 +161,11 @@ class Sort extends React.Component {
Sort.propTypes = {
qsConfig: QSConfig.isRequired,
columns: SortColumns.isRequired,
onSort: PropTypes.func
onSort: PropTypes.func,
};
Sort.defaultProps = {
onSort: null
onSort: null,
};
export default withI18n()(withRouter(Sort));

View File

@ -30,11 +30,7 @@ describe('<Sort />', () => {
const onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
qsConfig={qsConfig}
columns={columns}
onSort={onSort}
/>
<Sort qsConfig={qsConfig} columns={columns} onSort={onSort} />
).find('Sort');
wrapper.find(sortBtn).simulate('click');
@ -62,17 +58,13 @@ describe('<Sort />', () => {
{
name: 'Bakery',
key: 'bakery',
}
},
];
const onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
qsConfig={qsConfig}
columns={columns}
onSort={onSort}
/>
<Sort qsConfig={qsConfig} columns={columns} onSort={onSort} />
).find('Sort');
const sortDropdownToggle = wrapper.find('Button');
expect(sortDropdownToggle.length).toBe(1);
@ -99,17 +91,13 @@ describe('<Sort />', () => {
{
name: 'Bakery',
key: 'bakery',
}
},
];
const onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
qsConfig={qsConfig}
columns={columns}
onSort={onSort}
/>
<Sort qsConfig={qsConfig} columns={columns} onSort={onSort} />
).find('Sort');
const sortDropdownToggle = wrapper.find('Button');
expect(sortDropdownToggle.length).toBe(1);
@ -136,17 +124,13 @@ describe('<Sort />', () => {
{
name: 'Bakery',
key: 'bakery',
}
},
];
const onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
qsConfig={qsConfig}
columns={columns}
onSort={onSort}
/>
<Sort qsConfig={qsConfig} columns={columns} onSort={onSort} />
).find('Sort');
wrapper.instance().handleDropdownSelect({ target: { innerText: 'Bar' } });
@ -172,17 +156,13 @@ describe('<Sort />', () => {
{
name: 'Bakery',
key: 'bakery',
}
},
];
const onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
qsConfig={qsConfig}
columns={columns}
onSort={onSort}
/>
<Sort qsConfig={qsConfig} columns={columns} onSort={onSort} />
).find('Sort');
expect(wrapper.state('isSortDropdownOpen')).toEqual(false);
wrapper.instance().handleDropdownToggle(true);
@ -216,12 +196,8 @@ describe('<Sort />', () => {
integerFields: ['page', 'page_size'],
};
const numericColumns = [
{ name: 'ID', key: 'id' },
];
const alphaColumns = [
{ name: 'Name', key: 'name' },
];
const numericColumns = [{ name: 'ID', key: 'id' }];
const alphaColumns = [{ name: 'Name', key: 'name' }];
const onSort = jest.fn();
sort = mountWithContexts(
@ -236,11 +212,7 @@ describe('<Sort />', () => {
expect(downNumericIcon.length).toBe(1);
sort = mountWithContexts(
<Sort
qsConfig={qsConfigNumUp}
columns={numericColumns}
onSort={onSort}
/>
<Sort qsConfig={qsConfigNumUp} columns={numericColumns} onSort={onSort} />
);
const upNumericIcon = sort.find(upNumericIconSelector);
@ -258,11 +230,7 @@ describe('<Sort />', () => {
expect(downAlphaIcon.length).toBe(1);
sort = mountWithContexts(
<Sort
qsConfig={qsConfigAlphaUp}
columns={alphaColumns}
onSort={onSort}
/>
<Sort qsConfig={qsConfigAlphaUp} columns={alphaColumns} onSort={onSort} />
);
const upAlphaIcon = sort.find(upAlphaIconSelector);

View File

@ -194,7 +194,7 @@ class HostsList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),

View File

@ -181,12 +181,12 @@ function InventoryGroupsList({ i18n, location, match }) {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Is Root Group`),
key: 'parents__isnull',
isBoolean: true
isBoolean: true,
},
{
name: i18n._(t`Created By (Username)`),
@ -200,8 +200,8 @@ function InventoryGroupsList({ i18n, location, match }) {
toolbarSortColumns={[
{
name: i18n._(t`Name`),
key: 'name'
}
key: 'name',
},
]}
renderItem={item => (
<InventoryGroupItem

View File

@ -161,7 +161,9 @@ describe('<InventoryGroupsList />', () => {
});
wrapper.update();
await act(async () => {
wrapper.find('DataToolbar Button[aria-label="Delete"]').invoke('onClick')();
wrapper
.find('DataToolbar Button[aria-label="Delete"]')
.invoke('onClick')();
});
await waitForElement(
wrapper,
@ -193,7 +195,9 @@ describe('<InventoryGroupsList />', () => {
});
wrapper.update();
await act(async () => {
wrapper.find('DataToolbar Button[aria-label="Delete"]').invoke('onClick')();
wrapper
.find('DataToolbar Button[aria-label="Delete"]')
.invoke('onClick')();
});
await waitForElement(
wrapper,

View File

@ -136,7 +136,7 @@ function InventoryHosts({ i18n, location, match }) {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),
@ -150,8 +150,8 @@ function InventoryHosts({ i18n, location, match }) {
toolbarSortColumns={[
{
name: i18n._(t`Name`),
key: 'name'
}
key: 'name',
},
]}
renderToolbar={props => (
<DataListToolbar

View File

@ -178,7 +178,7 @@ class InventoriesList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),

View File

@ -23,12 +23,16 @@ import { getQSConfig, parseQueryString } from '@util/qs';
import JobListItem from './JobListItem';
const QS_CONFIG = getQSConfig('job', {
page: 1,
page_size: 20,
order_by: '-finished',
not__launch_type: 'sync',
}, ['page', 'page_size', 'id']);
const QS_CONFIG = getQSConfig(
'job',
{
page: 1,
page_size: 20,
order_by: '-finished',
not__launch_type: 'sync',
},
['page', 'page_size', 'id']
);
class JobList extends Component {
constructor(props) {
@ -167,88 +171,46 @@ class JobList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`ID`),
key: 'id'
key: 'id',
},
{
name: i18n._(t`Label Name`),
key: 'labels__name'
key: 'labels__name',
},
{
name: i18n._(t`Job Type`),
key: `type`,
options: [
[
`project_update`,
i18n._(t`SCM Update`)
],
[
`inventory_update`,
i18n._(t`Inventory Sync`)
],
[
`job`,
i18n._(t`Playbook Run`)
],
[
`ad_hoc_command`,
i18n._(t`Command`)
],
[
`system_job`,
i18n._(t`Management Job`)
],
[
`workflow_job`,
i18n._(t`Workflow Job`)
]
]
[`project_update`, i18n._(t`SCM Update`)],
[`inventory_update`, i18n._(t`Inventory Sync`)],
[`job`, i18n._(t`Playbook Run`)],
[`ad_hoc_command`, i18n._(t`Command`)],
[`system_job`, i18n._(t`Management Job`)],
[`workflow_job`, i18n._(t`Workflow Job`)],
],
},
{
name: i18n._(t`Created By (Username)`),
key: 'created_by__username'
key: 'created_by__username',
},
{
name: i18n._(t`Status`),
key: 'status',
options: [
[
`new`,
i18n._(t`New`)
],
[
`pending`,
i18n._(t`Pending`)
],
[
`waiting`,
i18n._(t`Waiting`)
],
[
`running`,
i18n._(t`Running`)
],
[
`successful`,
i18n._(t`Successful`)
],
[
`failed`,
i18n._(t`Failed`)
],
[
`error`,
i18n._(t`Error`)
],
[
`canceled`,
i18n._(t`Canceled`)
]
]
}
[`new`, i18n._(t`New`)],
[`pending`, i18n._(t`Pending`)],
[`waiting`, i18n._(t`Waiting`)],
[`running`, i18n._(t`Running`)],
[`successful`, i18n._(t`Successful`)],
[`failed`, i18n._(t`Failed`)],
[`error`, i18n._(t`Error`)],
[`canceled`, i18n._(t`Canceled`)],
],
},
]}
toolbarSortColumns={[
{

View File

@ -123,7 +123,7 @@ function OrganizationsList({ i18n }) {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),

View File

@ -51,7 +51,7 @@ function OrganizationTeams({ id, i18n }) {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Created by (username)`),

View File

@ -160,32 +160,18 @@ class ProjectsList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Type`),
key: 'type',
options: [
[
``,
i18n._(t`Manual`)
],
[
`git`,
i18n._(t`Git`)
],
[
`hg`,
i18n._(t`Mercurial`)
],
[
`svn`,
i18n._(t`Subversion`)
],
[
`insights`,
i18n._(t`Red Hat Insights`)
]
]
[``, i18n._(t`Manual`)],
[`git`, i18n._(t`Git`)],
[`hg`, i18n._(t`Mercurial`)],
[`svn`, i18n._(t`Subversion`)],
[`insights`, i18n._(t`Red Hat Insights`)],
],
},
{
name: i18n._(t`SCM URL`),
@ -204,7 +190,7 @@ class ProjectsList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
}
},
]}
renderToolbar={props => (
<DataListToolbar

View File

@ -158,7 +158,7 @@ class TeamsList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Organization Name`),

View File

@ -218,21 +218,15 @@ class TemplatesList extends Component {
{
name: i18n._(t`Name`),
key: 'name',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`Type`),
key: 'type',
options: [
[
`job_template`,
i18n._(t`Job Template`)
],
[
`workflow_job_template`,
i18n._(t`Workflow Template`)
],
]
[`job_template`, i18n._(t`Job Template`)],
[`workflow_job_template`, i18n._(t`Workflow Template`)],
],
},
{
name: i18n._(t`Playbook name`),

View File

@ -158,7 +158,7 @@ class UsersList extends Component {
{
name: i18n._(t`Username`),
key: 'username',
isDefault: true
isDefault: true,
},
{
name: i18n._(t`First Name`),
@ -167,7 +167,7 @@ class UsersList extends Component {
{
name: i18n._(t`Last Name`),
key: 'last_name',
}
},
]}
toolbarSortColumns={[
{

View File

@ -257,7 +257,7 @@ export const SearchColumns = arrayOf(
key: string.isRequired,
isDefault: bool,
isBoolean: bool,
options: arrayOf(arrayOf(string, string))
options: arrayOf(arrayOf(string, string)),
})
);

View File

@ -15,7 +15,7 @@ export function getQSConfig(
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) {
if (!defaultParams.order_by) {
defaultParams.order_by = 'name';
}
return {
@ -193,7 +193,6 @@ function removeParam(oldVal, deleteVal) {
* @return {object} merged namespaced params object
*/
export function mergeParams(oldParams, newParams) {
debugger;
const merged = {};
Object.keys(oldParams).forEach(key => {
merged[key] = mergeParam(oldParams[key], newParams[key]);

View File

@ -122,10 +122,12 @@ 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({
expect(
getQSConfig('organization', {
page: 1,
page_size: 5,
})
).toEqual({
namespace: 'organization',
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
integerFields: ['page', 'page_size'],