diff --git a/src/web/entity/EntityComponent.jsx b/src/web/entity/EntityComponent.jsx index 7293557e82..dab9a69300 100644 --- a/src/web/entity/EntityComponent.jsx +++ b/src/web/entity/EntityComponent.jsx @@ -4,11 +4,10 @@ */ import {isDefined} from 'gmp/utils/identity'; -import {useDispatch} from 'react-redux'; +import useEntityDelete from 'web/entity/hooks/useEntityDelete'; import useEntityDownload from 'web/entity/hooks/useEntityDownload'; import {actionFunction} from 'web/entity/hooks/utils'; import useGmp from 'web/hooks/useGmp'; -import {createDeleteEntity} from 'web/store/entities/utils/actions'; import PropTypes from 'web/utils/PropTypes'; const EntityComponent = ({ @@ -27,10 +26,7 @@ const EntityComponent = ({ onCloneError, }) => { const gmp = useGmp(); - const dispatch = useDispatch(); const cmd = gmp[name]; - const deleteEntity = entity => - dispatch(createDeleteEntity({entityType: name})(gmp)(entity.id)); const handleInteraction = () => { if (isDefined(onInteraction)) { @@ -48,11 +44,11 @@ const EntityComponent = ({ return actionFunction(cmd.create(data), onCreated, onCreateError); }; - const handleEntityDelete = async entity => { - handleInteraction(); - - return actionFunction(deleteEntity(entity), onDeleted, onDeleteError); - }; + const handleEntityDelete = useEntityDelete(name, { + onDeleteError, + onDeleted, + onInteraction, + }); const handleEntityClone = async entity => { handleInteraction(); diff --git a/src/web/entity/hooks/__tests__/useEntityDelete.tests.js b/src/web/entity/hooks/__tests__/useEntityDelete.tests.js new file mode 100644 index 0000000000..38f1fa7eb4 --- /dev/null +++ b/src/web/entity/hooks/__tests__/useEntityDelete.tests.js @@ -0,0 +1,62 @@ +/* SPDX-FileCopyrightText: 2025 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, test, expect, testing} from '@gsa/testing'; +import useEntityDelete from 'web/entity/hooks/useEntityDelete'; +import {rendererWith, wait} from 'web/utils/Testing'; + +describe('useEntityDelete', () => { + test('should allow to delete an entity', async () => { + const entity = {id: '123'}; + const deleteEntity = testing.fn().mockResolvedValue(entity); + const gmp = { + foo: {delete: deleteEntity}, + }; + const onDeleted = testing.fn(); + const onDeleteError = testing.fn(); + const onInteraction = testing.fn(); + const {renderHook} = rendererWith({gmp, store: true}); + const {result} = renderHook(() => + useEntityDelete('foo', { + onDeleted, + onDeleteError, + onInteraction, + }), + ); + expect(result.current).toBeDefined; + result.current(entity); + await wait(); + expect(deleteEntity).toHaveBeenCalledWith(entity); + expect(onDeleted).toHaveBeenCalledOnce(); + expect(onDeleteError).not.toHaveBeenCalled(); + expect(onInteraction).toHaveBeenCalledOnce(); + }); + + test('should call onDeleteError when deleting an entity fails', async () => { + const deleteEntity = testing.fn().mockRejectedValue(new Error('error')); + const entity = {id: '123'}; + const gmp = { + foo: {delete: deleteEntity}, + }; + const onDeleted = testing.fn(); + const onDeleteError = testing.fn(); + const onInteraction = testing.fn(); + const {renderHook} = rendererWith({gmp, store: true}); + const {result} = renderHook(() => + useEntityDelete('foo', { + onDeleted, + onDeleteError, + onInteraction, + }), + ); + expect(result.current).toBeDefined; + result.current(entity); + await wait(); + expect(deleteEntity).toHaveBeenCalledWith(entity); + expect(onDeleted).not.toHaveBeenCalled(); + expect(onDeleteError).toHaveBeenCalledOnce(); + expect(onInteraction).toHaveBeenCalledOnce(); + }); +}); diff --git a/src/web/entity/hooks/useEntityDelete.js b/src/web/entity/hooks/useEntityDelete.js new file mode 100644 index 0000000000..26bbc66592 --- /dev/null +++ b/src/web/entity/hooks/useEntityDelete.js @@ -0,0 +1,45 @@ +/* SPDX-FileCopyrightText: 2025 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {isDefined} from 'gmp/utils/identity'; +import {useDispatch} from 'react-redux'; +import {actionFunction} from 'web/entity/hooks/utils'; +import useGmp from 'web/hooks/useGmp'; +import {createDeleteEntity} from 'web/store/entities/utils/actions'; + +/** + * Custom hook to handle the deletion of an entity. + * + * @param {string} name - The name of the entity type to be deleted. + * @param {Object} [callbacks] - Optional callbacks for handling delete events. + * @param {Function} [callbacks.onDeleteError] - Callback function to be called if there is an error during deletion. + * @param {Function} [callbacks.onDeleted] - Callback function to be called after the entity is successfully deleted. + * @param {Function} [callbacks.onInteraction] - Callback function to be called during interaction. + * @returns {Function} - A function to handle the deletion of an entity. + */ +const useEntityDelete = ( + name, + {onDeleteError, onDeleted, onInteraction} = {}, +) => { + const gmp = useGmp(); + const dispatch = useDispatch(); + const deleteEntity = entity => + dispatch(createDeleteEntity({entityType: name})(gmp)(entity.id)); + + const handleInteraction = () => { + if (isDefined(onInteraction)) { + onInteraction(); + } + }; + + const handleEntityDelete = async entity => { + handleInteraction(); + + return actionFunction(deleteEntity(entity), onDeleted, onDeleteError); + }; + return handleEntityDelete; +}; + +export default useEntityDelete;