Migrate filters to qs lib

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2020-11-05 15:39:57 +01:00
parent 586781e12e
commit 944e34043c
6 changed files with 164 additions and 123 deletions

View File

@ -1,4 +1,4 @@
import React, { memo, useMemo, useReducer } from 'react'; import React, { memo, useCallback, useMemo, useReducer, useRef } from 'react';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { capitalize, get } from 'lodash'; import { capitalize, get } from 'lodash';
@ -9,9 +9,11 @@ import {
getFilterType, getFilterType,
useUser, useUser,
findMatchingPermissions, findMatchingPermissions,
useGlobalContext,
} from 'strapi-helper-plugin'; } from 'strapi-helper-plugin';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import { formatFiltersToQuery, getTrad } from '../../utils';
import useListView from '../../hooks/useListView'; import useListView from '../../hooks/useListView';
import Container from '../Container'; import Container from '../Container';
import FilterPickerOption from '../FilterPickerOption'; import FilterPickerOption from '../FilterPickerOption';
@ -21,8 +23,9 @@ import reducer, { initialState } from './reducer';
const NOT_ALLOWED_FILTERS = ['json', 'component', 'relation', 'media', 'richtext', 'dynamiczone']; const NOT_ALLOWED_FILTERS = ['json', 'component', 'relation', 'media', 'richtext', 'dynamiczone'];
function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState }) { function FilterPicker({ contentType, isOpen, name, toggleFilterPickerState, setQuery, slug }) {
const { schema, filters, slug } = useListView(); const { emitEvent } = useGlobalContext();
const emitEventRef = useRef(emitEvent);
const userPermissions = useUser(); const userPermissions = useUser();
const readActionAllowedFields = useMemo(() => { const readActionAllowedFields = useMemo(() => {
const matchingPermissions = findMatchingPermissions(userPermissions, [ const matchingPermissions = findMatchingPermissions(userPermissions, [
@ -34,15 +37,32 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
return get(matchingPermissions, ['0', 'fields'], []); return get(matchingPermissions, ['0', 'fields'], []);
}, [userPermissions, slug]); }, [userPermissions, slug]);
let timestamps = get(schema, ['options', 'timestamps']);
let timestamps = get(contentType, ['options', 'timestamps']);
if (!Array.isArray(timestamps)) { if (!Array.isArray(timestamps)) {
timestamps = []; timestamps = [];
} }
const allowedAttributes = Object.keys(get(schema, ['attributes']), {}) const actions = [
{
label: getTrad('components.FiltersPickWrapper.PluginHeader.actions.clearAll'),
kind: 'secondary',
onClick: () => {
toggleFilterPickerState();
setQuery({ _where: [] }, 'remove');
},
},
{
label: getTrad('components.FiltersPickWrapper.PluginHeader.actions.apply'),
kind: 'primary',
type: 'submit',
},
];
const allowedAttributes = Object.keys(get(contentType, ['attributes']), {})
.filter(attr => { .filter(attr => {
const current = get(schema, ['attributes', attr], {}); const current = get(contentType, ['attributes', attr], {});
if (!readActionAllowedFields.includes(attr) && attr !== 'id' && !timestamps.includes(attr)) { if (!readActionAllowedFields.includes(attr) && attr !== 'id' && !timestamps.includes(attr)) {
return false; return false;
@ -52,7 +72,7 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
}) })
.sort() .sort()
.map(attr => { .map(attr => {
const current = get(schema, ['attributes', attr], {}); const current = get(contentType, ['attributes', attr], {});
return { name: attr, type: current.type, options: current.enum || null }; return { name: attr, type: current.type, options: current.enum || null };
}); });
@ -81,8 +101,7 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
</FormattedMessage> </FormattedMessage>
); );
// Generate the first filter for adding a new one or at initial state const initialFilter = useMemo(() => {
const getInitialFilter = () => {
const type = get(allowedAttributes, [0, 'type'], ''); const type = get(allowedAttributes, [0, 'type'], '');
const [filter] = getFilterType(type); const [filter] = getFilterType(type);
@ -103,36 +122,44 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
}; };
return initFilter; return initFilter;
}; }, [allowedAttributes]);
// Set the filters when the collapse is opening // Set the filters when the collapse is opening
const handleEntering = () => { const handleEntering = () => {
const currentFilters = filters; const currentFilters = filters;
const initialFilters = currentFilters.length > 0 ? currentFilters : [getInitialFilter()]; /* eslint-disable indent */
const initialFilters = currentFilters.length ? currentFilters : [initialFilter];
dispatch({ dispatch({
type: 'SET_FILTERS', type: 'SET_FILTERS',
initialFilters, initialFilters,
attributes: get(schema, 'attributes', {}), attributes: get(contentType, 'attributes', {}),
}); });
}; };
const addFilter = () => { const addFilter = () => {
dispatch({ dispatch({
type: 'ADD_FILTER', type: 'ADD_FILTER',
filter: getInitialFilter(), filter: initialFilter,
}); });
}; };
const handleSubmit = useCallback(
e => {
e.preventDefault();
const nextFilters = formatFiltersToQuery(modifiedData);
emitEventRef.current('didFilterEntries');
setQuery(nextFilters);
toggleFilterPickerState();
},
[modifiedData, setQuery, toggleFilterPickerState]
);
return ( return (
<Collapse isOpen={isOpen} onEntering={handleEntering}> <Collapse isOpen={isOpen} onEntering={handleEntering}>
<Container style={{ backgroundColor: 'white', paddingBottom: 0 }}> <Container style={{ backgroundColor: 'white', paddingBottom: 0 }}>
<form <form onSubmit={handleSubmit}>
onSubmit={e => {
e.preventDefault();
onSubmit(modifiedData);
}}
>
<PluginHeader <PluginHeader
actions={actions} actions={actions}
title={renderTitle} title={renderTitle}
@ -161,7 +188,7 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
index, index,
}); });
}} }}
type={get(schema, ['attributes', filter.name, 'type'], '')} type={get(contentType, ['attributes', filter.name, 'type'], '')}
showAddButton={key === modifiedData.length - 1} showAddButton={key === modifiedData.length - 1}
// eslint-disable-next-line react/no-array-index-key // eslint-disable-next-line react/no-array-index-key
key={key} key={key}
@ -181,19 +208,15 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
} }
FilterPicker.defaultProps = { FilterPicker.defaultProps = {
actions: [],
isOpen: false,
name: '', name: '',
}; };
FilterPicker.propTypes = { FilterPicker.propTypes = {
actions: PropTypes.array, contentType: PropTypes.object.isRequired,
isOpen: PropTypes.bool, isOpen: PropTypes.bool.isRequired,
location: PropTypes.shape({
search: PropTypes.string.isRequired,
}).isRequired,
name: PropTypes.string, name: PropTypes.string,
onSubmit: PropTypes.func.isRequired, setQuery: PropTypes.func.isRequired,
slug: PropTypes.string.isRequired,
toggleFilterPickerState: PropTypes.func.isRequired, toggleFilterPickerState: PropTypes.func.isRequired,
}; };

View File

@ -1,22 +1,22 @@
import React from 'react'; import React, { memo, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { get, toString } from 'lodash'; import { get, toString } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { FilterButton } from 'strapi-helper-plugin'; import { FilterButton } from 'strapi-helper-plugin';
import dateFormats from '../../utils/dateFormats'; import { dateFormats, formatFiltersToQuery } from '../../utils';
function Filter({ function Filter({
changeParams, contentType,
filter, filterName,
filters, filters,
index, index,
name, name,
schema,
value, value,
toggleFilterPickerState, toggleFilterPickerState,
isFilterPickerOpen, isFilterPickerOpen,
setQuery,
}) { }) {
const type = get(schema, ['attributes', name, 'type'], 'string'); const type = get(contentType, ['attributes', name, 'type'], 'string');
let displayedValue = toString(value); let displayedValue = toString(value);
if (type.includes('date') || type.includes('timestamp')) { if (type.includes('date') || type.includes('timestamp')) {
@ -36,26 +36,24 @@ function Filter({
.format(format); .format(format);
} }
console.log({ name });
const label = { const label = {
name, name,
filter, filter: filterName,
value: displayedValue, value: displayedValue,
}; };
return ( const handleClick = useCallback(() => {
<FilterButton const updatedFilters = filters.slice().filter((_, i) => i !== index);
onClick={() => {
const updatedFilters = filters.slice().filter((_, i) => i !== index);
if (isFilterPickerOpen) { if (isFilterPickerOpen) {
toggleFilterPickerState(); toggleFilterPickerState();
} }
changeParams({ target: { name: 'filters', value: updatedFilters } });
}} setQuery(formatFiltersToQuery(updatedFilters));
label={label} }, [filters, index, isFilterPickerOpen, setQuery, toggleFilterPickerState]);
type={type}
/> return <FilterButton onClick={handleClick} label={label} type={type} />;
);
} }
Filter.defaultProps = { Filter.defaultProps = {
@ -64,15 +62,15 @@ Filter.defaultProps = {
}; };
Filter.propTypes = { Filter.propTypes = {
changeParams: PropTypes.func.isRequired, contentType: PropTypes.shape({ attributes: PropTypes.object.isRequired }).isRequired,
filter: PropTypes.string.isRequired, filterName: PropTypes.string.isRequired,
filters: PropTypes.array.isRequired, filters: PropTypes.array.isRequired,
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
isFilterPickerOpen: PropTypes.bool.isRequired, isFilterPickerOpen: PropTypes.bool.isRequired,
name: PropTypes.string, name: PropTypes.string,
schema: PropTypes.object.isRequired, setQuery: PropTypes.func.isRequired,
toggleFilterPickerState: PropTypes.func.isRequired, toggleFilterPickerState: PropTypes.func.isRequired,
value: PropTypes.any, value: PropTypes.any,
}; };
export default Filter; export default memo(Filter);

View File

@ -18,13 +18,18 @@ import {
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import pluginPermissions from '../../permissions'; import pluginPermissions from '../../permissions';
import { useQueryParams } from '../../hooks'; import { useQueryParams } from '../../hooks';
import { generatePermissionsObject, getRequestUrl, getTrad } from '../../utils'; import {
formatFiltersFromQuery,
generatePermissionsObject,
getRequestUrl,
getTrad,
} from '../../utils';
import DisplayedFieldsDropdown from '../../components/DisplayedFieldsDropdown'; import DisplayedFieldsDropdown from '../../components/DisplayedFieldsDropdown';
import Container from '../../components/Container'; import Container from '../../components/Container';
import CustomTable from '../../components/CustomTable'; import CustomTable from '../../components/CustomTable';
// import FilterPicker from '../../components/FilterPicker'; import FilterPicker from '../../components/FilterPicker';
import Search from '../../components/Search'; import Search from '../../components/Search';
import ListViewProvider from '../ListViewProvider'; import ListViewProvider from '../ListViewProvider';
import { AddFilterCta, FilterIcon, Wrapper } from './components'; import { AddFilterCta, FilterIcon, Wrapper } from './components';
@ -53,7 +58,7 @@ import { getAllAllowedHeaders, getFirstSortableHeader } from './utils';
/* eslint-disable react/no-array-index-key */ /* eslint-disable react/no-array-index-key */
const FilterPicker = () => <div>FILTER</div>; // const FilterPicker = () => <div>FILTER</div>;
function ListView({ function ListView({
count, count,
@ -105,12 +110,12 @@ function ListView({
} = layout; } = layout;
const { emitEvent } = useGlobalContext(); const { emitEvent } = useGlobalContext();
const emitEventRef = useRef(emitEvent);
const viewPermissions = useMemo(() => generatePermissionsObject(slug), [slug]); const viewPermissions = useMemo(() => generatePermissionsObject(slug), [slug]);
const { const {
isLoading: isLoadingForPermissions, isLoading: isLoadingForPermissions,
allowedActions: { canCreate, canRead, canUpdate, canDelete }, allowedActions: { canCreate, canRead, canUpdate, canDelete },
} = useUserPermissions(viewPermissions); } = useUserPermissions(viewPermissions);
// const query = useQuery();
const defaultSort = `${defaultSortBy}:${defaultSortOrder}`; const defaultSort = `${defaultSortBy}:${defaultSortOrder}`;
const [{ query, rawQuery }, setQuery] = useQueryParams({ const [{ query, rawQuery }, setQuery] = useQueryParams({
page: 1, page: 1,
@ -132,7 +137,9 @@ function ListView({
const allAllowedHeaders = getAllAllowedHeaders(attributes); const allAllowedHeaders = getAllAllowedHeaders(attributes);
const filters = query._where || []; const filters = useMemo(() => {
return formatFiltersFromQuery(query);
}, [query]);
const _limit = parseInt(query.pageSize, 10); const _limit = parseInt(query.pageSize, 10);
const _page = parseInt(query.page, 10); const _page = parseInt(query.page, 10);
@ -142,18 +149,6 @@ function ListView({
const label = contentType.info.label; const label = contentType.info.label;
const searchToSendForRequest = rawQuery; const searchToSendForRequest = rawQuery;
// const searchToSendForRequest = useMemo(() => {
// const currentSearch = new URLSearchParams(search);
// currentSearch.set('_limit', _limit);
// currentSearch.set('_sort', _sort);
// currentSearch.set('_start', _start);
// currentSearch.delete('_page');
// return currentSearch.toString();
// }, [_limit, _sort, _start, search]);
// const getDataActionRef = useRef(getData);
const getDataSucceededRef = useRef(getDataSucceeded); const getDataSucceededRef = useRef(getDataSucceeded);
const shouldSendRequest = useMemo(() => { const shouldSendRequest = useMemo(() => {
@ -327,24 +322,6 @@ function ListView({
[displayedHeaders, onChangeListHeaders] [displayedHeaders, onChangeListHeaders]
); );
// const handleChangeFilters = ({ target: { value } }) => {
// const newSearch = new URLSearchParams();
// // Set the default params
// newSearch.set('_limit', _limit);
// newSearch.set('_sort', _sort);
// newSearch.set('_page', 1);
// value.forEach(({ filter, name, value: filterValue }) => {
// const filterType = filter === '=' ? '' : filter;
// const filterName = `${name}${filterType}`;
// newSearch.append(filterName, filterValue);
// });
// push({ search: newSearch.toString() });
// };
const handleChangeSearch = async ({ target: { name, value } }) => { const handleChangeSearch = async ({ target: { name, value } }) => {
const currentSearch = new URLSearchParams(searchToSendForRequest); const currentSearch = new URLSearchParams(searchToSendForRequest);
@ -373,36 +350,15 @@ function ListView({
} }
}; };
const handleSubmit = (filters = []) => { const toggleFilterPickerState = useCallback(() => {
emitEvent('didFilterEntries'); setFilterPickerState(prevState => {
toggleFilterPickerState(); if (!prevState) {
// handleChangeFilters({ target: { name: 'filters', value: filters } }); emitEventRef.current('willFilterEntries');
}; }
const toggleFilterPickerState = () => { return !prevState;
if (!isFilterPickerOpen) { });
emitEvent('willFilterEntries'); }, []);
}
setFilterPickerState(prevState => !prevState);
};
const filterPickerActions = [
{
label: `${pluginId}.components.FiltersPickWrapper.PluginHeader.actions.clearAll`,
kind: 'secondary',
onClick: () => {
toggleFilterPickerState();
// Delete all filters
// handleChangeFilters({ target: { name: 'filters', value: [] } });
},
},
{
label: `${pluginId}.components.FiltersPickWrapper.PluginHeader.actions.apply`,
kind: 'primary',
type: 'submit',
},
];
const headerAction = useMemo( const headerAction = useMemo(
() => { () => {
@ -494,11 +450,13 @@ function ListView({
firstSortableHeader={firstSortableHeader} firstSortableHeader={firstSortableHeader}
> >
<FilterPicker <FilterPicker
actions={filterPickerActions} contentType={contentType}
isOpen={isFilterPickerOpen} isOpen={isFilterPickerOpen}
name={label} name={label}
toggleFilterPickerState={toggleFilterPickerState} toggleFilterPickerState={toggleFilterPickerState}
onSubmit={handleSubmit} setQuery={setQuery}
filters={filters}
slug={slug}
/> />
<Container className="container-fluid"> <Container className="container-fluid">
{!isFilterPickerOpen && <Header {...headerProps} isLoading={isLoading && canRead} />} {!isFilterPickerOpen && <Header {...headerProps} isLoading={isLoading && canRead} />}
@ -516,16 +474,17 @@ function ListView({
<FilterIcon /> <FilterIcon />
<FormattedMessage id="app.utils.filters" /> <FormattedMessage id="app.utils.filters" />
</AddFilterCta> </AddFilterCta>
{filters.map((filter, key) => ( {filters.map(({ filter: filterName, name }, key) => (
<Filter <Filter
{...filter} contentType={contentType}
// changeParams={handleChangeFilters} filterName={filterName}
filters={filters} filters={filters}
index={key} index={key}
schema={{}}
key={key} key={key}
name={name}
toggleFilterPickerState={toggleFilterPickerState} toggleFilterPickerState={toggleFilterPickerState}
isFilterPickerOpen={isFilterPickerOpen} isFilterPickerOpen={isFilterPickerOpen}
setQuery={setQuery}
/> />
))} ))}
</> </>

View File

@ -0,0 +1,46 @@
const findAppliedFilter = str => {
let filter = '=';
let name = str;
const filters = [
'_ne',
'_lt',
'_lte',
'_gt',
'_gte',
'_contains',
'_containss',
'_ncontains',
'_in',
'_nin',
];
filters.forEach(filterName => {
const split = str.split(filterName);
if (split[1] === '') {
filter = filterName;
name = split[0];
}
});
return { filter, name };
};
const formatFiltersFromQuery = ({ _where }) => {
if (!_where) {
return [];
}
return _where.map(obj => {
const [key] = Object.keys(obj);
const { filter, name } = findAppliedFilter(key);
const value = obj[key];
return { name, filter, value };
});
};
export default formatFiltersFromQuery;
export { findAppliedFilter };

View File

@ -0,0 +1,13 @@
const formatFiltersToQuery = array => {
const nextFilters = array.map(({ name, filter, value }) => {
if (filter === '=') {
return { [name]: value };
}
return { [`${name}${filter}`]: value };
});
return { _where: nextFilters };
};
export default formatFiltersToQuery;

View File

@ -2,6 +2,8 @@ export { default as checkIfAttributeIsDisplayable } from './checkIfAttributeIsDi
export { default as createDefaultForm } from './createDefaultForm'; export { default as createDefaultForm } from './createDefaultForm';
export { default as dateFormats } from './dateFormats'; export { default as dateFormats } from './dateFormats';
export { default as formatComponentData } from './formatComponentData'; export { default as formatComponentData } from './formatComponentData';
export { default as formatFiltersFromQuery } from './formatFiltersFromQuery';
export { default as formatFiltersToQuery } from './formatFiltersToQuery';
export { default as generatePermissionsObject } from './generatePermissionsObject'; export { default as generatePermissionsObject } from './generatePermissionsObject';
export { default as getInjectedComponents } from './getComponents'; export { default as getInjectedComponents } from './getComponents';
export { default as getMaxTempKey } from './getMaxTempKey'; export { default as getMaxTempKey } from './getMaxTempKey';