diff --git a/packages/volto/news/6414.feature b/packages/volto/news/6414.feature new file mode 100644 index 0000000000..9583c6da10 --- /dev/null +++ b/packages/volto/news/6414.feature @@ -0,0 +1,3 @@ +URL Management control panel: add a filter for redirects created after a given date. +**Note:** This requires `Products.CMFPlone` 6.0.14 and `plone.restapi` 9.7.3 or later. +@davisagli diff --git a/packages/volto/src/actions/aliases/aliases.js b/packages/volto/src/actions/aliases/aliases.js index 368353f2d8..0131ab6a79 100644 --- a/packages/volto/src/actions/aliases/aliases.js +++ b/packages/volto/src/actions/aliases/aliases.js @@ -16,17 +16,19 @@ import { * @param {Object} options Options data. * @returns {Object} Get aliases action. */ -export function getAliases(url, options) { - const { query, manual, datetime, batchSize, batchStart } = options || {}; +export function getAliases(url, options = {}) { + const { query, batchSize, batchStart, ...rest } = options; + const params = new URLSearchParams({ + q: query ?? '', + b_start: batchStart ?? 0, + b_size: batchSize ?? 99999999999, + ...rest, + }); return { type: GET_ALIASES, request: { op: 'get', - path: `${url}/@aliases?q=${query ? query : ''}&manual=${ - manual ? manual : '' - }&datetime=${datetime !== null ? datetime : ''}&b_size=${ - batchSize ? batchSize : 99999999999 - }&b_start=${batchStart ? batchStart : 0}`, + path: `${url}/@aliases?${params.toString()}`, }, }; } diff --git a/packages/volto/src/actions/aliases/aliases.test.js b/packages/volto/src/actions/aliases/aliases.test.js index e419b58a6a..e04783cead 100644 --- a/packages/volto/src/actions/aliases/aliases.test.js +++ b/packages/volto/src/actions/aliases/aliases.test.js @@ -14,7 +14,7 @@ describe('Aliases action', () => { expect(action.type).toEqual(GET_ALIASES); expect(action.request.op).toEqual('get'); expect(action.request.path).toEqual( - '/news/@aliases?q=&manual=&datetime=undefined&b_size=99999999999&b_start=0', + '/news/@aliases?q=&b_start=0&b_size=99999999999', ); }); }); diff --git a/packages/volto/src/components/manage/Controlpanels/Aliases.jsx b/packages/volto/src/components/manage/Controlpanels/Aliases.jsx index 1bc0dddd6d..edc54c63bb 100644 --- a/packages/volto/src/components/manage/Controlpanels/Aliases.jsx +++ b/packages/volto/src/components/manage/Controlpanels/Aliases.jsx @@ -1,12 +1,7 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Link, useHistory, useLocation } from 'react-router-dom'; -import { - getBaseUrl, - getParentUrl, - Helmet, - usePrevious, -} from '@plone/volto/helpers'; +import { getBaseUrl, getParentUrl, Helmet } from '@plone/volto/helpers'; import { removeAliases, addAliases, getAliases } from '@plone/volto/actions'; import { createPortal } from 'react-dom'; import { @@ -67,217 +62,113 @@ const Aliases = (props) => { const { pathname } = useLocation(); const history = useHistory(); + const hasAdvancedFiltering = useSelector( + (state) => state.site.data?.features?.filter_aliases_by_date, + ); const aliases = useSelector((state) => state.aliases); const [filterType, setFilterType] = useState(filterChoices[0]); const [createdBefore, setCreatedBefore] = useState(null); + const [createdAfter, setCreatedAfter] = useState(null); const [altUrlPath, setAltUrlPath] = useState(''); - const [isAltUrlCorrect, setIsAltUrlCorrect] = useState(false); const [targetUrlPath, setTargetUrlPath] = useState(''); const [aliasesToRemove, setAliasesToRemove] = useState([]); - const [errorMessageAdd, setErrorMessageAdd] = useState(''); const [filterQuery, setFilterQuery] = useState(''); const [activePage, setActivePage] = useState(1); - const [pages, setPages] = useState(''); const [itemsPerPage, setItemsPerPage] = useState(10); const isClient = useClient(); - const prevaliasesitemstotal = usePrevious(aliases.items_total); - const previtemsPerPage = usePrevious(itemsPerPage); - const prevactivePage = usePrevious(activePage); - const prevalturlpath = usePrevious(altUrlPath); - const prevtargetUrlPath = usePrevious(targetUrlPath); - const prevaliasesaddloading = usePrevious(aliases.add.loading); - const prevaliasesremoveloading = usePrevious(aliases.remove.loading); - - useEffect(() => { - if ( - prevaliasesitemstotal !== aliases.items_total || - previtemsPerPage !== itemsPerPage - ) { - const pages = Math.ceil(aliases.items_total / itemsPerPage); - - if (pages === 0 || isNaN(pages)) { - setPages(''); - } else { - setPages(pages); - } - } - if (prevactivePage !== activePage || previtemsPerPage !== itemsPerPage) { - dispatch( - getAliases(getBaseUrl(pathname), { - query: filterQuery, - manual: filterType.value, - datetime: createdBefore, - batchSize: itemsPerPage === 'All' ? 999999999999 : itemsPerPage, - batchStart: (activePage - 1) * itemsPerPage, - }), - ); - } - if (prevalturlpath !== altUrlPath) { - if (altUrlPath.charAt(0) === '/') { - setIsAltUrlCorrect(true); - } else { - setIsAltUrlCorrect(false); - } + const updateResults = useCallback(() => { + const options = { + query: filterQuery, + manual: filterType.value, + batchStart: (activePage - 1) * itemsPerPage, + batchSize: itemsPerPage === 'All' ? 999999999999 : itemsPerPage, + }; + if (hasAdvancedFiltering) { + options.start = createdAfter || ''; + options.end = createdBefore || ''; + } else { + options.datetime = createdBefore || ''; } + dispatch(getAliases(getBaseUrl(pathname), options)); }, [ - itemsPerPage, - pathname, - prevaliasesitemstotal, - aliases.items_total, - previtemsPerPage, - prevactivePage, activePage, - prevalturlpath, - altUrlPath, - prevtargetUrlPath, - targetUrlPath, - dispatch, - filterQuery, - filterType.value, + createdAfter, createdBefore, - ]); - - useEffect(() => { - if (prevaliasesaddloading && !aliases.add.loaded) { - if (aliases.add.error) { - setErrorMessageAdd(aliases.add.error.response.body.message); - } - } - if (prevaliasesaddloading && aliases.add.loaded) { - dispatch( - getAliases(getBaseUrl(pathname), { - query: filterQuery, - manual: filterType.value, - datetime: createdBefore, - batchSize: itemsPerPage, - }), - ); - toast.success( - , - ); - if (!aliases.add.error) { - setErrorMessageAdd(''); - } - } - if (prevaliasesremoveloading && aliases.remove.loaded) { - dispatch( - getAliases(getBaseUrl(pathname), { - query: filterQuery, - manual: filterType.value, - datetime: createdBefore, - batchSize: itemsPerPage, - }), - ); - } - }, [ - prevaliasesaddloading, - aliases.add.loaded, - aliases.add.error, - aliases.remove.loaded, - prevaliasesremoveloading, dispatch, - pathname, filterQuery, filterType.value, - createdBefore, + hasAdvancedFiltering, itemsPerPage, - intl, + pathname, ]); - const onCancel = () => { - history.push(getParentUrl(pathname)); - }; + // Update results after changing the page. + // (We intentionally leave updateResults out of the deps.) + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(() => updateResults(), [activePage, itemsPerPage]); - const handleSelectFilterType = (type) => { - setFilterType(type); - }; + // Calculate page count from results + const pages = useMemo(() => { + let pages = Math.ceil(aliases.items_total / itemsPerPage); + if (pages === 0 || isNaN(pages)) { + pages = ''; + } + return pages; + }, [aliases.items_total, itemsPerPage]); - const handleFilterQueryChange = (query) => { - setFilterQuery(query); - }; + // Validate altUrlPath starts with a slash + const isAltUrlCorrect = useMemo(() => { + return Boolean(altUrlPath.charAt(0) === '/'); + }, [altUrlPath]); - const handleCreateDate = (date) => { - setCreatedBefore(date); - }; + // Check for errors on add + const errorMessageAdd = aliases.add.error?.response?.body?.message; - const handleSubmitFilter = () => { + // Add new alias + const handleSubmitAlias = useCallback(() => { dispatch( - getAliases(getBaseUrl(pathname), { - query: filterQuery, - manual: filterType.value, - datetime: createdBefore, - batchSize: itemsPerPage, + addAliases('', { + items: [ + { + path: altUrlPath, + 'redirect-to': targetUrlPath, + }, + ], }), - ); - }; - - const handleAltUrlChange = (url) => { - setAltUrlPath(url); - }; - - const handleTargetUrlChange = (url) => { - setTargetUrlPath(url); - }; - - const handleSubmitAlias = useCallback(() => { - if (isAltUrlCorrect) { - dispatch( - addAliases('', { - items: [ - { - path: altUrlPath, - 'redirect-to': targetUrlPath, - }, - ], - }), - ); + ).then(() => { + updateResults(); setAltUrlPath(''); setTargetUrlPath(''); - } - }, [isAltUrlCorrect, altUrlPath, targetUrlPath, dispatch]); + toast.success( + , + ); + }); + }, [altUrlPath, targetUrlPath, dispatch, intl, updateResults]); + // Check/uncheck an alias const handleCheckAlias = (alias) => { - const aliasess = [...aliasesToRemove]; - if (aliasess.includes(alias)) { - const index = aliasess.indexOf(alias); - if (index > -1) { - let newAliasesArr = aliasess; - newAliasesArr.splice(index, 1); - setAliasesToRemove(newAliasesArr); - } + if (aliasesToRemove.includes(alias)) { + setAliasesToRemove(aliasesToRemove.filter((x) => x !== alias)); } else { setAliasesToRemove([...aliasesToRemove, alias]); } }; - const handleRemoveAliases = () => { - const items = aliasesToRemove.map((a) => { - return { - path: a, - }; - }); + // Remove selected aliases + const handleRemoveAliases = () => { dispatch( removeAliases('', { - items, + items: aliasesToRemove.map((a) => ({ path: a })), }), - ); + ).then(updateResults); setAliasesToRemove([]); }; - const handlePageChange = (e, { activePage }) => { - setActivePage(activePage); - }; - - const handleItemsPerPage = (e, { value }) => { - setItemsPerPage(value); - setActivePage(1); - }; - return (
@@ -311,7 +202,7 @@ const Aliases = (props) => { name="alternative-url-path" placeholder="/example" value={altUrlPath} - onChange={(e) => handleAltUrlChange(e.target.value)} + onChange={(e) => setAltUrlPath(e.target.value)} /> {!isAltUrlCorrect && altUrlPath !== '' && (

@@ -340,7 +231,7 @@ const Aliases = (props) => { name="target-url-path" placeholder="/example" value={targetUrlPath} - onChange={(e) => handleTargetUrlChange(e.target.value)} + onChange={(e) => setTargetUrlPath(e.target.value)} />

@@ -492,7 +396,9 @@ const Aliases = (props) => { lastItem={null} siblingRange={1} totalPages={pages} - onPageChange={(e, o) => handlePageChange(e, o)} + onPageChange={(e, { activePage }) => + setActivePage(activePage) + } /> )} { key={size} value={size} active={size === itemsPerPage} - onClick={(e, o) => handleItemsPerPage(e, o)} + onClick={(e, { value }) => { + setItemsPerPage(value); + setActivePage(1); + }} > {size} @@ -540,7 +449,11 @@ const Aliases = (props) => { pathname={pathname} hideDefaultViewButtons inner={ - onCancel()}> + history.push(getParentUrl(pathname))} + > { locale: 'en', messages: {}, }, + site: { + data: { + features: { + filter_aliases_by_date: true, + }, + }, + }, }); const { container } = render( diff --git a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap index fda6016602..3342eef8ac 100644 --- a/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap +++ b/packages/volto/src/components/manage/Controlpanels/__snapshots__/Aliases.test.jsx.snap @@ -181,6 +181,9 @@ exports[`Aliases renders an aliases control component 1`] = `
+