From 5b3872f6329cd0fc971d9ce26c6336f00504eee0 Mon Sep 17 00:00:00 2001 From: Jeff McMillen Date: Wed, 18 Dec 2024 11:27:39 -0600 Subject: [PATCH] Task/WP-731: Mutation hook: Trash file/folder (#994) * Converted useTrash mutation to use React Query and TypeScript * Linted code to meet quality standards * Removed superfluous comments * Removed extraneous dispatch calls and ran mutations on each selected file * Added more asynchronous calls to further streamline process * Still in the process of getting the trash mutation to work correctly * Trash mutation actually works as intended * Linted client-side code * Reverting changes made to package and package-lock * Refined trash mutation hook to use Axios and Promises * Linted client-side code again * Ensured a Promise is returned --------- Co-authored-by: Jeff McMillen Co-authored-by: Sal Tijerina --- .../DataFilesToolbar/DataFilesToolbar.jsx | 23 ++-- .../src/hooks/datafiles/mutations/useTrash.js | 29 ---- .../src/hooks/datafiles/mutations/useTrash.ts | 127 ++++++++++++++++++ 3 files changed, 137 insertions(+), 42 deletions(-) delete mode 100644 client/src/hooks/datafiles/mutations/useTrash.js create mode 100644 client/src/hooks/datafiles/mutations/useTrash.ts diff --git a/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx b/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx index 9f4fd9255..685073bbe 100644 --- a/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx +++ b/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx @@ -7,6 +7,7 @@ import getFilePermissions from 'utils/filePermissions'; import { useModal, useSelectedFiles, useFileListing } from 'hooks/datafiles'; import { useSystemRole } from '../DataFilesProjectMembers/_cells/SystemRoleSelector'; import './DataFilesToolbar.scss'; +import { useTrash } from 'hooks/datafiles/mutations'; export const ToolbarButton = ({ text, iconName, onClick, disabled }) => { const iconClassName = `action icon-${iconName}`; @@ -40,6 +41,7 @@ const DataFilesToolbar = ({ scheme, api }) => { const { toggle } = useModal(); const { selectedFiles } = useSelectedFiles(); const { params } = useFileListing('FilesListing'); + const { trash } = useTrash(); const history = useHistory(); const location = useLocation(); @@ -172,20 +174,15 @@ const DataFilesToolbar = ({ scheme, api }) => { } }; - const trash = useCallback(() => { - const filteredSelected = selectedFiles.filter( - (f) => status[f.system + f.path] !== 'SUCCESS' - ); + const homeDir = selectedSystem?.homeDir; - dispatch({ - type: 'DATA_FILES_TRASH', - payload: { - src: filteredSelected, - homeDir: selectedSystem?.homeDir || '', - reloadCallback: reloadPage, - }, + const trashCallback = useCallback(() => { + trash({ + destSystem: selectedSystem.system, + homeDir: homeDir, + callback: reloadPage, }); - }, [selectedFiles, selectedSystem, reloadPage]); + }, [selectedFiles, reloadPage, status]); const empty = () => { dispatch({ @@ -271,7 +268,7 @@ const DataFilesToolbar = ({ scheme, api }) => { diff --git a/client/src/hooks/datafiles/mutations/useTrash.js b/client/src/hooks/datafiles/mutations/useTrash.js deleted file mode 100644 index 8280b82fd..000000000 --- a/client/src/hooks/datafiles/mutations/useTrash.js +++ /dev/null @@ -1,29 +0,0 @@ -import { useSelector, useDispatch, shallowEqual } from 'react-redux'; - -function useTrash() { - const dispatch = useDispatch(); - const status = useSelector( - (state) => state.files.operationStatus.trash, - shallowEqual - ); - - const setStatus = (newStatus) => { - dispatch({ - type: 'DATA_FILES_SET_OPERATION_STATUS', - payload: { status: newStatus, operation: 'trash' }, - }); - }; - - const trash = ({ selection, callback }) => - dispatch({ - type: 'DATA_FILES_TRASH', - payload: { - src: selection, - reloadCallback: callback, - }, - }); - - return { trash, status, setStatus }; -} - -export default useTrash; diff --git a/client/src/hooks/datafiles/mutations/useTrash.ts b/client/src/hooks/datafiles/mutations/useTrash.ts new file mode 100644 index 000000000..546d2dfc8 --- /dev/null +++ b/client/src/hooks/datafiles/mutations/useTrash.ts @@ -0,0 +1,127 @@ +import { useSelector, useDispatch, shallowEqual } from 'react-redux'; +import { useMutation } from '@tanstack/react-query'; +import { useSelectedFiles } from 'hooks/datafiles'; +import Cookies from 'js-cookie'; +import { apiClient } from 'utils/apiClient'; + +export async function trashUtil({ + api, + scheme, + system, + path, + homeDir, +}: { + api: string; + scheme: string; + system: string; + path: string; + homeDir: string; +}): Promise<{ file: any; path: string }> { + const url = `/api/datafiles/${api}/trash/${scheme}/${system}/${path}/`; + const body = { + homeDir: homeDir, + }; + const response = await apiClient.put(url, body, { + headers: { + 'X-CSRFToken': Cookies.get('csrftoken' || ''), + }, + withCredentials: true, + }); + + return response.data; +} + +function useTrash() { + const dispatch = useDispatch(); + const { selectedFiles: selected } = useSelectedFiles(); + const status = useSelector( + (state: any) => state.files.operationStatus.trash, + shallowEqual + ); + + const { api, scheme } = useSelector( + (state: any) => state.files.params.FilesListing + ); + + const setStatus = (newStatus: any) => { + dispatch({ + type: 'DATA_FILES_SET_OPERATION_STATUS', + payload: { status: newStatus, operation: 'trash' }, + }); + }; + + const { mutateAsync } = useMutation({ mutationFn: trashUtil }); + + const trash = ({ + destSystem, + homeDir, + callback, + }: { + destSystem: string; + homeDir: any; + callback: any; + }) => { + const filteredSelected = selected.filter( + (f: any) => status[f.id] !== 'SUCCESS' + ); + const trashCalls: Promise[] = filteredSelected.map((file: any) => { + dispatch({ + type: 'DATA_FILES_SET_OPERATION_STATUS_BY_KEY', + payload: { + status: 'RUNNING', + key: (index: string) => index, + operation: 'trash', + }, + }); + return mutateAsync( + { + api: api, + scheme: scheme, + system: destSystem, + path: file.path, + homeDir: homeDir, + }, + { + onSuccess: (response: any) => { + dispatch({ + type: 'DATA_FILES_SET_OPERATION_STATUS_BY_KEY', + payload: { + status: 'SUCCESS', + key: (index: string) => index, + operation: 'trash', + }, + }); + + callback(); + }, + onError: () => { + dispatch({ + type: 'DATA_FILES_SET_OPERATION_STATUS_BY_KEY', + payload: { + status: 'ERROR', + key: (index: string) => index, + operation: 'trash', + }, + }); + }, + } + ); + }); + Promise.all(trashCalls).then(() => { + dispatch({ + type: 'ADD_TOAST', + payload: { + message: `${ + filteredSelected.length > 1 + ? `${filteredSelected.length} files moved to Trash` + : 'File moved to Trash' + }`, + }, + }); + }); + }; + + return { trash, status, setStatus }; +} + +export default useTrash;