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 PropTypes from 'prop-types';
import { capitalize, get } from 'lodash';
@ -9,9 +9,11 @@ import {
getFilterType,
useUser,
findMatchingPermissions,
useGlobalContext,
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import { formatFiltersToQuery, getTrad } from '../../utils';
import useListView from '../../hooks/useListView';
import Container from '../Container';
import FilterPickerOption from '../FilterPickerOption';
@ -21,8 +23,9 @@ import reducer, { initialState } from './reducer';
const NOT_ALLOWED_FILTERS = ['json', 'component', 'relation', 'media', 'richtext', 'dynamiczone'];
function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState }) {
const { schema, filters, slug } = useListView();
function FilterPicker({ contentType, isOpen, name, toggleFilterPickerState, setQuery, slug }) {
const { emitEvent } = useGlobalContext();
const emitEventRef = useRef(emitEvent);
const userPermissions = useUser();
const readActionAllowedFields = useMemo(() => {
const matchingPermissions = findMatchingPermissions(userPermissions, [
@ -34,15 +37,32 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
return get(matchingPermissions, ['0', 'fields'], []);
}, [userPermissions, slug]);
let timestamps = get(schema, ['options', 'timestamps']);
let timestamps = get(contentType, ['options', 'timestamps']);
if (!Array.isArray(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 => {
const current = get(schema, ['attributes', attr], {});
const current = get(contentType, ['attributes', attr], {});
if (!readActionAllowedFields.includes(attr) && attr !== 'id' && !timestamps.includes(attr)) {
return false;
@ -52,7 +72,7 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
})
.sort()
.map(attr => {
const current = get(schema, ['attributes', attr], {});
const current = get(contentType, ['attributes', attr], {});
return { name: attr, type: current.type, options: current.enum || null };
});
@ -81,8 +101,7 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
</FormattedMessage>
);
// Generate the first filter for adding a new one or at initial state
const getInitialFilter = () => {
const initialFilter = useMemo(() => {
const type = get(allowedAttributes, [0, 'type'], '');
const [filter] = getFilterType(type);
@ -103,36 +122,44 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
};
return initFilter;
};
}, [allowedAttributes]);
// Set the filters when the collapse is opening
const handleEntering = () => {
const currentFilters = filters;
const initialFilters = currentFilters.length > 0 ? currentFilters : [getInitialFilter()];
/* eslint-disable indent */
const initialFilters = currentFilters.length ? currentFilters : [initialFilter];
dispatch({
type: 'SET_FILTERS',
initialFilters,
attributes: get(schema, 'attributes', {}),
attributes: get(contentType, 'attributes', {}),
});
};
const addFilter = () => {
dispatch({
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 (
<Collapse isOpen={isOpen} onEntering={handleEntering}>
<Container style={{ backgroundColor: 'white', paddingBottom: 0 }}>
<form
onSubmit={e => {
e.preventDefault();
onSubmit(modifiedData);
}}
>
<form onSubmit={handleSubmit}>
<PluginHeader
actions={actions}
title={renderTitle}
@ -161,7 +188,7 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
index,
});
}}
type={get(schema, ['attributes', filter.name, 'type'], '')}
type={get(contentType, ['attributes', filter.name, 'type'], '')}
showAddButton={key === modifiedData.length - 1}
// eslint-disable-next-line react/no-array-index-key
key={key}
@ -181,19 +208,15 @@ function FilterPicker({ actions, isOpen, name, onSubmit, toggleFilterPickerState
}
FilterPicker.defaultProps = {
actions: [],
isOpen: false,
name: '',
};
FilterPicker.propTypes = {
actions: PropTypes.array,
isOpen: PropTypes.bool,
location: PropTypes.shape({
search: PropTypes.string.isRequired,
}).isRequired,
contentType: PropTypes.object.isRequired,
isOpen: PropTypes.bool.isRequired,
name: PropTypes.string,
onSubmit: PropTypes.func.isRequired,
setQuery: PropTypes.func.isRequired,
slug: PropTypes.string.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 { get, toString } from 'lodash';
import moment from 'moment';
import { FilterButton } from 'strapi-helper-plugin';
import dateFormats from '../../utils/dateFormats';
import { dateFormats, formatFiltersToQuery } from '../../utils';
function Filter({
changeParams,
filter,
contentType,
filterName,
filters,
index,
name,
schema,
value,
toggleFilterPickerState,
isFilterPickerOpen,
setQuery,
}) {
const type = get(schema, ['attributes', name, 'type'], 'string');
const type = get(contentType, ['attributes', name, 'type'], 'string');
let displayedValue = toString(value);
if (type.includes('date') || type.includes('timestamp')) {
@ -36,26 +36,24 @@ function Filter({
.format(format);
}
console.log({ name });
const label = {
name,
filter,
filter: filterName,
value: displayedValue,
};
return (
<FilterButton
onClick={() => {
const updatedFilters = filters.slice().filter((_, i) => i !== index);
const handleClick = useCallback(() => {
const updatedFilters = filters.slice().filter((_, i) => i !== index);
if (isFilterPickerOpen) {
toggleFilterPickerState();
}
changeParams({ target: { name: 'filters', value: updatedFilters } });
}}
label={label}
type={type}
/>
);
if (isFilterPickerOpen) {
toggleFilterPickerState();
}
setQuery(formatFiltersToQuery(updatedFilters));
}, [filters, index, isFilterPickerOpen, setQuery, toggleFilterPickerState]);
return <FilterButton onClick={handleClick} label={label} type={type} />;
}
Filter.defaultProps = {
@ -64,15 +62,15 @@ Filter.defaultProps = {
};
Filter.propTypes = {
changeParams: PropTypes.func.isRequired,
filter: PropTypes.string.isRequired,
contentType: PropTypes.shape({ attributes: PropTypes.object.isRequired }).isRequired,
filterName: PropTypes.string.isRequired,
filters: PropTypes.array.isRequired,
index: PropTypes.number.isRequired,
isFilterPickerOpen: PropTypes.bool.isRequired,
name: PropTypes.string,
schema: PropTypes.object.isRequired,
setQuery: PropTypes.func.isRequired,
toggleFilterPickerState: PropTypes.func.isRequired,
value: PropTypes.any,
};
export default Filter;
export default memo(Filter);

View File

@ -18,13 +18,18 @@ import {
import pluginId from '../../pluginId';
import pluginPermissions from '../../permissions';
import { useQueryParams } from '../../hooks';
import { generatePermissionsObject, getRequestUrl, getTrad } from '../../utils';
import {
formatFiltersFromQuery,
generatePermissionsObject,
getRequestUrl,
getTrad,
} from '../../utils';
import DisplayedFieldsDropdown from '../../components/DisplayedFieldsDropdown';
import Container from '../../components/Container';
import CustomTable from '../../components/CustomTable';
// import FilterPicker from '../../components/FilterPicker';
import FilterPicker from '../../components/FilterPicker';
import Search from '../../components/Search';
import ListViewProvider from '../ListViewProvider';
import { AddFilterCta, FilterIcon, Wrapper } from './components';
@ -53,7 +58,7 @@ import { getAllAllowedHeaders, getFirstSortableHeader } from './utils';
/* eslint-disable react/no-array-index-key */
const FilterPicker = () => <div>FILTER</div>;
// const FilterPicker = () => <div>FILTER</div>;
function ListView({
count,
@ -105,12 +110,12 @@ function ListView({
} = layout;
const { emitEvent } = useGlobalContext();
const emitEventRef = useRef(emitEvent);
const viewPermissions = useMemo(() => generatePermissionsObject(slug), [slug]);
const {
isLoading: isLoadingForPermissions,
allowedActions: { canCreate, canRead, canUpdate, canDelete },
} = useUserPermissions(viewPermissions);
// const query = useQuery();
const defaultSort = `${defaultSortBy}:${defaultSortOrder}`;
const [{ query, rawQuery }, setQuery] = useQueryParams({
page: 1,
@ -132,7 +137,9 @@ function ListView({
const allAllowedHeaders = getAllAllowedHeaders(attributes);
const filters = query._where || [];
const filters = useMemo(() => {
return formatFiltersFromQuery(query);
}, [query]);
const _limit = parseInt(query.pageSize, 10);
const _page = parseInt(query.page, 10);
@ -142,18 +149,6 @@ function ListView({
const label = contentType.info.label;
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 shouldSendRequest = useMemo(() => {
@ -327,24 +322,6 @@ function ListView({
[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 currentSearch = new URLSearchParams(searchToSendForRequest);
@ -373,36 +350,15 @@ function ListView({
}
};
const handleSubmit = (filters = []) => {
emitEvent('didFilterEntries');
toggleFilterPickerState();
// handleChangeFilters({ target: { name: 'filters', value: filters } });
};
const toggleFilterPickerState = useCallback(() => {
setFilterPickerState(prevState => {
if (!prevState) {
emitEventRef.current('willFilterEntries');
}
const toggleFilterPickerState = () => {
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',
},
];
return !prevState;
});
}, []);
const headerAction = useMemo(
() => {
@ -494,11 +450,13 @@ function ListView({
firstSortableHeader={firstSortableHeader}
>
<FilterPicker
actions={filterPickerActions}
contentType={contentType}
isOpen={isFilterPickerOpen}
name={label}
toggleFilterPickerState={toggleFilterPickerState}
onSubmit={handleSubmit}
setQuery={setQuery}
filters={filters}
slug={slug}
/>
<Container className="container-fluid">
{!isFilterPickerOpen && <Header {...headerProps} isLoading={isLoading && canRead} />}
@ -516,16 +474,17 @@ function ListView({
<FilterIcon />
<FormattedMessage id="app.utils.filters" />
</AddFilterCta>
{filters.map((filter, key) => (
{filters.map(({ filter: filterName, name }, key) => (
<Filter
{...filter}
// changeParams={handleChangeFilters}
contentType={contentType}
filterName={filterName}
filters={filters}
index={key}
schema={{}}
key={key}
name={name}
toggleFilterPickerState={toggleFilterPickerState}
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 dateFormats } from './dateFormats';
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 getInjectedComponents } from './getComponents';
export { default as getMaxTempKey } from './getMaxTempKey';