From 494fe24c4232857fbb1f82db72112bdf80226a6a Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Fri, 6 Dec 2024 15:09:35 +0100 Subject: [PATCH 1/5] improve link integrity popup --- src/components/manage/Contents/Contents.jsx | 357 +---------------- .../manage/Contents/ContentsDeleteModal.jsx | 379 ++++++++++++++++++ src/components/manage/Rules/Rules.jsx | 8 +- src/reducers/index.js | 2 + src/reducers/linkIntegrity/linkIntegrity.js | 51 +++ .../linkIntegrity/linkIntegrity.test.js | 54 +++ 6 files changed, 498 insertions(+), 353 deletions(-) create mode 100644 src/components/manage/Contents/ContentsDeleteModal.jsx create mode 100644 src/reducers/linkIntegrity/linkIntegrity.js create mode 100644 src/reducers/linkIntegrity/linkIntegrity.test.js diff --git a/src/components/manage/Contents/Contents.jsx b/src/components/manage/Contents/Contents.jsx index cd1781e84d..8b9849a02c 100644 --- a/src/components/manage/Contents/Contents.jsx +++ b/src/components/manage/Contents/Contents.jsx @@ -11,7 +11,6 @@ import { Portal } from 'react-portal'; import { Link } from 'react-router-dom'; import { Button, - Confirm, Container as SemanticContainer, Divider, Dropdown, @@ -35,7 +34,6 @@ import { import move from 'lodash-move'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { asyncConnect } from '@plone/volto/helpers'; -import { flattenToAppURL } from '@plone/volto/helpers'; import { searchContent, @@ -48,7 +46,6 @@ import { orderContent, sortContent, updateColumnsContent, - linkIntegrityCheck, getContent, } from '@plone/volto/actions'; import Indexes, { defaultIndexes } from '@plone/volto/constants/Indexes'; @@ -68,6 +65,7 @@ import { Icon, Unauthorized, } from '@plone/volto/components'; +import ContentsDeleteModal from '@plone/volto/components/manage/Contents/ContentsDeleteModal'; import { Helmet, getBaseUrl } from '@plone/volto/helpers'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; @@ -119,14 +117,6 @@ const messages = defineMessages({ id: 'Delete', defaultMessage: 'Delete', }, - deleteConfirmSingleItem: { - id: 'Delete this item?', - defaultMessage: 'Delete this item?', - }, - deleteConfirmMultipleItems: { - id: 'Delete selected items?', - defaultMessage: 'Delete selected items?', - }, deleteError: { id: 'The item could not be deleted.', defaultMessage: 'The item could not be deleted.', @@ -300,7 +290,6 @@ class Contents extends Component { orderContent: PropTypes.func.isRequired, sortContent: PropTypes.func.isRequired, updateColumnsContent: PropTypes.func.isRequired, - linkIntegrityCheck: PropTypes.func.isRequired, clipboardRequest: PropTypes.shape({ loading: PropTypes.bool, loaded: PropTypes.bool, @@ -399,7 +388,6 @@ class Contents extends Component { this.paste = this.paste.bind(this); this.fetchContents = this.fetchContents.bind(this); this.orderTimeout = null; - this.deleteItemsToShowThreshold = 10; this.state = { selected: [], @@ -410,10 +398,6 @@ class Contents extends Component { showProperties: false, showWorkflow: false, itemsToDelete: [], - containedItemsToDelete: [], - brokenReferences: 0, - breaches: [], - showAllItemsToDelete: true, items: this.props.items, filter: '', currentPage: 0, @@ -429,7 +413,6 @@ class Contents extends Component { sort_on: this.props.sort?.on || 'getObjPositionInParent', sort_order: this.props.sort?.order || 'ascending', isClient: false, - linkIntegrityBreakages: [], }; this.filterTimeout = null; } @@ -443,50 +426,6 @@ class Contents extends Component { this.fetchContents(); this.setState({ isClient: true }); } - async componentDidUpdate(_, prevState) { - if ( - this.state.itemsToDelete !== prevState.itemsToDelete && - this.state.itemsToDelete.length > 0 - ) { - const linkintegrityInfo = await this.props.linkIntegrityCheck( - map(this.state.itemsToDelete, (item) => this.getFieldById(item, 'UID')), - ); - const containedItems = linkintegrityInfo - .map((result) => result.items_total ?? 0) - .reduce((acc, value) => acc + value, 0); - const breaches = linkintegrityInfo.flatMap((result) => - result.breaches.map((source) => ({ - source: source, - target: result, - })), - ); - const source_by_uid = breaches.reduce( - (acc, value) => acc.set(value.source.uid, value.source), - new Map(), - ); - const by_source = breaches.reduce((acc, value) => { - if (acc.get(value.source.uid) === undefined) { - acc.set(value.source.uid, new Set()); - } - acc.get(value.source.uid).add(value.target); - return acc; - }, new Map()); - - this.setState({ - containedItemsToDelete: containedItems, - brokenReferences: by_source.size, - linksAndReferencesViewLink: linkintegrityInfo.length - ? linkintegrityInfo[0]['@id'] + '/links-to-item' - : null, - breaches: Array.from(by_source, (entry) => ({ - source: source_by_uid.get(entry[0]), - targets: Array.from(entry[1]), - })), - showAllItemsToDelete: - this.state.itemsToDelete.length < this.deleteItemsToShowThreshold, - }); - } - } /** * Component will receive props @@ -1208,296 +1147,12 @@ class Contents extends Component { />
- - {this.state.itemsToDelete.length > 1 ? ( - this.state.containedItemsToDelete > 0 ? ( - <> - - {this.state.containedItemsToDelete} - - ), - variation: ( - - {this.state.containedItemsToDelete === - 1 ? ( - - ) : ( - - )} - - ), - }} - /> - {this.state.brokenReferences > 0 && ( - <> -
- - {this.state.brokenReferences} - - ), - variation: ( - - {this.state.brokenReferences === 1 ? ( - - ) : ( - - )} - - ), - }} - /> - - )} - - ) : ( - <> - {this.state.brokenReferences > 0 && ( - <> - - {this.state.brokenReferences} - - ), - variation: ( - - {this.state.brokenReferences === 1 ? ( - - ) : ( - - )} - - ), - }} - /> - - )} - - ) - ) : this.state.containedItemsToDelete > 0 ? ( - <> - - {this.state.containedItemsToDelete} - - ), - variation: ( - - {this.state.containedItemsToDelete === 1 ? ( - - ) : ( - - )} - - ), - }} - /> - {this.state.brokenReferences > 0 && ( - <> -
- {this.state.brokenReferences} - ), - variation: ( - - {this.state.brokenReferences === 1 ? ( - - ) : ( - - )} - - ), - }} - /> -
- -
    - {this.state.breaches.map((breach) => ( -
  • - - {breach.source.title} - {' '} - refers to{' '} - {breach.targets - .map((target) => ( - - {target.title} - - )) - .reduce((result, item) => ( - <> - {result}, {item} - - ))} -
  • - ))} -
- {this.state.linksAndReferencesViewLink && ( - - - - )} -
- - )} - - ) : this.state.brokenReferences > 0 ? ( - <> - {this.state.brokenReferences} - ), - variation: ( - - {this.state.brokenReferences === 1 ? ( - - ) : ( - - )} - - ), - }} - /> -
- -
    - {this.state.breaches.map((breach) => ( -
  • - - {breach.source.title} - {' '} - refers to{' '} - {breach.targets - .map((target) => ( - - {target.title} - - )) - .reduce((result, item) => ( - <> - {result}, {item} - - ))} -
  • - ))} -
- {this.state.linksAndReferencesViewLink && ( - - - - )} -
- - ) : null} -
- } onCancel={this.onDeleteCancel} - onConfirm={this.onDeleteOk} - size="medium" + onOk={this.onDeleteOk} + items={this.state.items} + itemsToDelete={this.state.itemsToDelete} /> { + const { itemsToDelete = [], open, onCancel, onOk, items } = props; + const intl = useIntl(); + const dispatch = useDispatch(); + const linkintegrityInfo = useSelector((state) => state.linkIntegrity?.result); + const loading = useSelector((state) => state.linkIntegrity?.loading); + + const [brokenReferences, setBrokenReferences] = useState(0); + const [containedItemsToDelete, setContainedItemsToDelete] = useState([]); + const [breaches, setBreaches] = useState([]); + + const [linksAndReferencesViewLink, setLinkAndReferencesViewLink] = + useState(null); + + useEffect(() => { + const getFieldById = (id, field) => { + const item = find(items, { '@id': id }); + return item ? item[field] : ''; + }; + + if (itemsToDelete.length > 0 && open) { + dispatch( + linkIntegrityCheck( + map(itemsToDelete, (item) => getFieldById(item, 'UID')), + ), + ); + } + }, [itemsToDelete, items, open, dispatch]); + + useEffect(() => { + if (linkintegrityInfo) { + const containedItems = linkintegrityInfo + .map((result) => result.items_total ?? 0) + .reduce((acc, value) => acc + value, 0); + const breaches = linkintegrityInfo.flatMap((result) => + result.breaches.map((source) => ({ + source: source, + target: result, + })), + ); + const source_by_uid = breaches.reduce( + (acc, value) => acc.set(value.source.uid, value.source), + new Map(), + ); + const by_source = breaches.reduce((acc, value) => { + if (acc.get(value.source.uid) === undefined) { + acc.set(value.source.uid, new Set()); + } + acc.get(value.source.uid).add(value.target); + return acc; + }, new Map()); + + setContainedItemsToDelete(containedItems); + setBrokenReferences(by_source.size); + setLinkAndReferencesViewLink( + linkintegrityInfo.length + ? linkintegrityInfo[0]['@id'] + '/links-to-item' + : null, + ); + setBreaches( + Array.from(by_source, (entry) => ({ + source: source_by_uid.get(entry[0]), + targets: Array.from(entry[1]), + })), + ); + } else { + setContainedItemsToDelete([]); + setBrokenReferences(0); + setLinkAndReferencesViewLink(null); + setBreaches([]); + } + }, [linkintegrityInfo]); + + return ( + open && ( + + + + {intl.formatMessage(messages.loading)} + + + + {itemsToDelete.length > 1 ? ( + containedItemsToDelete > 0 ? ( + <> + {containedItemsToDelete} + ), + variation: ( + + {containedItemsToDelete === 1 ? ( + + ) : ( + + )} + + ), + }} + /> + {brokenReferences > 0 && ( + <> +
+ {brokenReferences}, + variation: ( + + {brokenReferences === 1 ? ( + + ) : ( + + )} + + ), + }} + /> + + )} + + ) : ( + <> + {brokenReferences > 0 && ( + <> + {brokenReferences}, + variation: ( + + {brokenReferences === 1 ? ( + + ) : ( + + )} + + ), + }} + /> + + )} + + ) + ) : containedItemsToDelete > 0 ? ( + <> + {containedItemsToDelete} + ), + variation: ( + + {containedItemsToDelete === 1 ? ( + + ) : ( + + )} + + ), + }} + /> + {brokenReferences > 0 && ( + <> +
+ {brokenReferences}, + variation: ( + + {brokenReferences === 1 ? ( + + ) : ( + + )} + + ), + }} + /> + + + )} + + ) : brokenReferences > 0 ? ( + <> + {brokenReferences}, + variation: ( + + {brokenReferences === 1 ? ( + + ) : ( + + )} + + ), + }} + /> + + + ) : null} + + } + onCancel={onCancel} + onConfirm={onOk} + size="medium" + /> + ) + ); +}; + +const BrokenLinksList = ({ intl, breaches, linksAndReferencesViewLink }) => { + return ( +
+ + : + + + {breaches.map((breach) => ( + + + + {breach.source.title} + + + + : + + +
    + {breach.targets.map((target) => ( +
  • + + {target.title} + +
  • + ))} +
+
+
+ ))} +
+
+ {linksAndReferencesViewLink && ( + + + + )} +
+ ); +}; +ContentsDeleteModal.propTypes = { + itemsToDelete: PropTypes.arrayOf( + PropTypes.shape({ + UID: PropTypes.string, + }), + ).isRequired, + open: PropTypes.bool.isRequired, + onOk: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; +export default ContentsDeleteModal; diff --git a/src/components/manage/Rules/Rules.jsx b/src/components/manage/Rules/Rules.jsx index cc0aacf11f..bab91b618e 100644 --- a/src/components/manage/Rules/Rules.jsx +++ b/src/components/manage/Rules/Rules.jsx @@ -75,6 +75,10 @@ const messages = defineMessages({ id: 'Unassigned', defaultMessage: 'Unassigned', }, + select_rule: { + id: 'Select rule', + defaultMessage: 'Select rule', + }, }); /** @@ -365,7 +369,9 @@ class Rules extends Component { />