mirror of
https://github.com/ansible/awx.git
synced 2026-01-17 12:41:19 -03:30
Merge pull request #4742 from jlmitch5/searchv2
ui_next search updates Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
b2b475d1a6
31
awx/ui_next/package-lock.json
generated
31
awx/ui_next/package-lock.json
generated
@ -13354,6 +13354,17 @@
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"scheduler": {
|
||||
"version": "0.13.6",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
|
||||
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-codemirror2": {
|
||||
@ -13370,6 +13381,17 @@
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"scheduler": {
|
||||
"version": "0.13.6",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
|
||||
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-fast-compare": {
|
||||
@ -14398,15 +14420,6 @@
|
||||
"xmlchars": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.13.6",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
|
||||
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz",
|
||||
|
||||
@ -15,6 +15,8 @@ import Search from '../Search';
|
||||
import Sort from '../Sort';
|
||||
import VerticalSeparator from '../VerticalSeparator';
|
||||
|
||||
import { QSConfig } from '@types';
|
||||
|
||||
const AWXToolbar = styled.div`
|
||||
--awx-toolbar--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
||||
--awx-toolbar--BorderColor: #ebebeb;
|
||||
@ -98,6 +100,7 @@ class DataListToolbar extends React.Component {
|
||||
sortedColumnKey,
|
||||
additionalControls,
|
||||
i18n,
|
||||
qsConfig,
|
||||
} = this.props;
|
||||
|
||||
const showExpandCollapse = onCompact && onExpand;
|
||||
@ -120,6 +123,7 @@ class DataListToolbar extends React.Component {
|
||||
)}
|
||||
<ToolbarItem css="flex-grow: 1;">
|
||||
<Search
|
||||
qsConfig={qsConfig}
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
@ -160,6 +164,7 @@ class DataListToolbar extends React.Component {
|
||||
}
|
||||
|
||||
DataListToolbar.propTypes = {
|
||||
qsConfig: QSConfig.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
showSelectAll: PropTypes.bool,
|
||||
isAllSelected: PropTypes.bool,
|
||||
|
||||
@ -5,6 +5,13 @@ import DataListToolbar from './DataListToolbar';
|
||||
describe('<DataListToolbar />', () => {
|
||||
let toolbar;
|
||||
|
||||
const QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
if (toolbar) {
|
||||
toolbar.unmount();
|
||||
@ -28,6 +35,7 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
isAllSelected={false}
|
||||
showExpandCollapse
|
||||
sortedColumnKey="name"
|
||||
@ -77,6 +85,7 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="foo"
|
||||
sortOrder="ascending"
|
||||
columns={multipleColumns}
|
||||
@ -151,6 +160,7 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="id"
|
||||
sortOrder="descending"
|
||||
columns={numericColumns}
|
||||
@ -173,6 +183,7 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
sortOrder="descending"
|
||||
columns={alphaColumns}
|
||||
@ -184,6 +195,7 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
sortOrder="ascending"
|
||||
columns={alphaColumns}
|
||||
@ -204,6 +216,7 @@ describe('<DataListToolbar />', () => {
|
||||
|
||||
toolbar = mountWithContexts(
|
||||
<DataListToolbar
|
||||
qsConfig={QS_CONFIG}
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
onSort={onSort}
|
||||
|
||||
@ -14,6 +14,8 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
import { SearchIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { QSConfig } from '@types';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
const TextInput = styled(PFTextInput)`
|
||||
@ -110,10 +112,19 @@ class Search extends React.Component {
|
||||
e.preventDefault();
|
||||
|
||||
const { searchKey, searchValue } = this.state;
|
||||
const { onSearch } = this.props;
|
||||
const { onSearch, qsConfig } = this.props;
|
||||
|
||||
// TODO: probably not _always_ add icontains. I don't think icontains works for numbers.
|
||||
onSearch(`${searchKey}__icontains`, searchValue);
|
||||
const isNonStringField =
|
||||
qsConfig.integerFields.filter(field => field === searchKey).length ||
|
||||
qsConfig.dateFields.filter(field => field === searchKey).length;
|
||||
|
||||
// TODO: this will probably become more sophisticated, where date
|
||||
// fields and string fields are passed to a formatter
|
||||
const actualSearchKey = isNonStringField
|
||||
? searchKey
|
||||
: `${searchKey}__icontains`;
|
||||
|
||||
onSearch(actualSearchKey, searchValue);
|
||||
|
||||
this.setState({ searchValue: '' });
|
||||
}
|
||||
@ -202,6 +213,7 @@ class Search extends React.Component {
|
||||
}
|
||||
|
||||
Search.propTypes = {
|
||||
qsConfig: QSConfig.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSearch: PropTypes.func,
|
||||
sortedColumnKey: PropTypes.string,
|
||||
|
||||
@ -5,6 +5,13 @@ import Search from './Search';
|
||||
describe('<Search />', () => {
|
||||
let search;
|
||||
|
||||
const QS_CONFIG = {
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
if (search) {
|
||||
search = null;
|
||||
@ -22,7 +29,12 @@ describe('<Search />', () => {
|
||||
const onSearch = jest.fn();
|
||||
|
||||
search = mountWithContexts(
|
||||
<Search sortedColumnKey="name" columns={columns} onSearch={onSearch} />
|
||||
<Search
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
);
|
||||
|
||||
search.find(searchTextInput).instance().value = 'test-321';
|
||||
@ -39,7 +51,12 @@ describe('<Search />', () => {
|
||||
];
|
||||
const onSearch = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<Search sortedColumnKey="name" columns={columns} onSearch={onSearch} />
|
||||
<Search
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
).find('Search');
|
||||
expect(wrapper.state('isSearchDropdownOpen')).toEqual(false);
|
||||
wrapper.instance().handleDropdownToggle(true);
|
||||
@ -58,7 +75,12 @@ describe('<Search />', () => {
|
||||
];
|
||||
const onSearch = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<Search sortedColumnKey="name" columns={columns} onSearch={onSearch} />
|
||||
<Search
|
||||
qsConfig={QS_CONFIG}
|
||||
sortedColumnKey="name"
|
||||
columns={columns}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
).find('Search');
|
||||
expect(wrapper.state('searchKey')).toEqual('name');
|
||||
wrapper
|
||||
|
||||
@ -183,6 +183,7 @@ class JobList extends Component {
|
||||
showExpandCollapse
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={this.handleSelectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
<ToolbarDeleteButton
|
||||
key="delete"
|
||||
|
||||
@ -190,6 +190,7 @@ class OrganizationAccess extends React.Component {
|
||||
renderToolbar={props => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={
|
||||
canEdit
|
||||
? [
|
||||
|
||||
@ -41,6 +41,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
items={Array []}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
@ -91,6 +95,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
items={Array []}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
@ -157,6 +165,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
@ -221,6 +233,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
itemCount={0}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
@ -278,6 +294,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
@ -329,6 +349,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
onRemoveAll={[Function]}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
@ -353,6 +377,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
onRemoveAll={[Function]}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
@ -391,6 +419,10 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
onRemoveAll={[Function]}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
|
||||
@ -181,6 +181,7 @@ class OrganizationsList extends Component {
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={this.handleSelectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
<ToolbarDeleteButton
|
||||
key="delete"
|
||||
|
||||
@ -67,6 +67,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
@ -137,6 +141,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
@ -222,6 +230,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
@ -286,6 +298,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
itemCount={2}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
@ -344,6 +360,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
@ -1713,6 +1733,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
onRemoveAll={[Function]}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
@ -1737,6 +1761,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
onRemoveAll={[Function]}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
@ -1775,6 +1803,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
||||
onRemoveAll={[Function]}
|
||||
qsConfig={
|
||||
Object {
|
||||
"dateFields": Array [
|
||||
"modified",
|
||||
"created",
|
||||
],
|
||||
"defaultParams": Object {
|
||||
"order_by": "name",
|
||||
"page": 1,
|
||||
|
||||
@ -65,6 +65,7 @@ describe('<OrganizationTeams />', () => {
|
||||
expect(list.prop('itemCount')).toEqual(listData.data.count);
|
||||
expect(list.prop('qsConfig')).toEqual({
|
||||
namespace: 'team',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
|
||||
@ -207,6 +207,7 @@ class TemplatesList extends Component {
|
||||
showExpandCollapse
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={this.handleSelectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
<ToolbarDeleteButton
|
||||
key="delete"
|
||||
|
||||
@ -161,7 +161,8 @@ export const encodeNonDefaultQueryString = (config, params) => {
|
||||
export function getQSConfig(
|
||||
namespace,
|
||||
defaultParams = { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields = ['page', 'page_size']
|
||||
integerFields = ['page', 'page_size'],
|
||||
dateFields = ['modified', 'created']
|
||||
) {
|
||||
if (!namespace) {
|
||||
throw new Error('a QS namespace is required');
|
||||
@ -170,6 +171,7 @@ export function getQSConfig(
|
||||
namespace,
|
||||
defaultParams,
|
||||
integerFields,
|
||||
dateFields,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -78,6 +78,7 @@ describe('qs (qs.js)', () => {
|
||||
test('should get default QS config object', () => {
|
||||
expect(getQSConfig('organization')).toEqual({
|
||||
namespace: 'organization',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
});
|
||||
@ -94,6 +95,7 @@ describe('qs (qs.js)', () => {
|
||||
};
|
||||
expect(getQSConfig('inventory', defaults)).toEqual({
|
||||
namespace: 'inventory',
|
||||
dateFields: ['modified', 'created'],
|
||||
defaultParams: { page: 1, page_size: 15 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user