21128: Merge branch 'main' into 21128-toolbar-context-menu
[arvados-workbench2.git] / src / store / workbench / workbench-actions.ts
index 1517f34c7e1d2dc222e6223b42dc94c01b8cb489..188dba05689edf9be63c7da67ed6c35c2db1df55 100644 (file)
@@ -8,12 +8,8 @@ import { getUserUuid } from "common/getuser";
 import { loadDetailsPanel } from "store/details-panel/details-panel-action";
 import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 import { favoritePanelActions, loadFavoritePanel } from "store/favorite-panel/favorite-panel-action";
-import {
-    getProjectPanelCurrentUuid,
-    openProjectPanel,
-    projectPanelActions,
-    setIsProjectPanelTrashed,
-} from "store/project-panel/project-panel-action";
+import { getProjectPanelCurrentUuid, setIsProjectPanelTrashed } from "store/project-panel/project-panel-action";
+import { projectPanelActions } from "store/project-panel/project-panel-action-bind";
 import {
     activateSidePanelTreeItem,
     initSidePanelTree,
@@ -54,6 +50,7 @@ import { trashPanelColumns } from "views/trash-panel/trash-panel";
 import { loadTrashPanel, trashPanelActions } from "store/trash-panel/trash-panel-action";
 import { loadProcessPanel } from "store/process-panel/process-panel-actions";
 import { loadSharedWithMePanel, sharedWithMePanelActions } from "store/shared-with-me-panel/shared-with-me-panel-actions";
+import { sharedWithMePanelColumns } from "views/shared-with-me-panel/shared-with-me-panel";
 import { CopyFormDialogData } from "store/copy-dialog/copy-dialog";
 import { workflowPanelActions } from "store/workflow-panel/workflow-panel-actions";
 import { loadSshKeysPanel } from "store/auth/auth-action-ssh";
@@ -100,7 +97,8 @@ import { loadAllProcessesPanel, allProcessesPanelActions } from "../all-processe
 import { allProcessesPanelColumns } from "views/all-processes-panel/all-processes-panel";
 import { AdminMenuIcon } from "components/icon/icon";
 import { userProfileGroupsColumns } from "views/user-profile-panel/user-profile-panel-root";
-import { selectedToArray, selectedToKindSet } from "components/multiselectToolbar/MultiselectToolbar";
+import { selectedToArray, selectedToKindSet } from "components/multiselect-toolbar/MultiselectToolbar";
+import { multiselectActions } from "store/multiselect/multiselect-actions";
 
 export const WORKBENCH_LOADING_SCREEN = "workbenchLoadingScreen";
 
@@ -112,6 +110,12 @@ export const isWorkbenchLoading = (state: RootState) => {
 export const handleFirstTimeLoad = (action: any) => async (dispatch: Dispatch<any>, getState: () => RootState) => {
     try {
         await dispatch(action);
+    } catch (e) {
+        snackbarActions.OPEN_SNACKBAR({
+            message: "Error " + e,
+            hideDuration: 8000,
+            kind: SnackbarKind.WARNING,
+        })
     } finally {
         if (isWorkbenchLoading(getState())) {
             dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
@@ -137,7 +141,7 @@ export const loadWorkbench = () => async (dispatch: Dispatch, getState: () => Ro
             })
         );
         dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
-        dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
+        dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: sharedWithMePanelColumns }));
         dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns }));
         dispatch(
             searchResultsPanelActions.SET_FETCH_MODE({
@@ -231,35 +235,40 @@ export const loadProject = (uuid: string) =>
         if (!userUuid) {
             return;
         }
-        if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
-            // Load another users home projects
-            dispatch(finishLoadingProject(uuid));
-        } else if (userUuid !== uuid) {
-            await dispatch(finishLoadingProject(uuid));
-            const match = await loadGroupContentsResource({
-                uuid,
-                userUuid,
-                services,
-            });
-            match({
-                OWNED: async () => {
-                    await dispatch(activateSidePanelTreeItem(uuid));
-                    dispatch<any>(setSidePanelBreadcrumbs(uuid));
-                },
-                SHARED: async () => {
-                    await dispatch(activateSidePanelTreeItem(uuid));
-                    dispatch<any>(setSharedWithMeBreadcrumbs(uuid));
-                },
-                TRASHED: async () => {
-                    await dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
-                    dispatch<any>(setTrashBreadcrumbs(uuid));
-                    dispatch(setIsProjectPanelTrashed(true));
-                },
-            });
-        } else {
-            await dispatch(finishLoadingProject(userUuid));
-            await dispatch(activateSidePanelTreeItem(userUuid));
-            dispatch<any>(setSidePanelBreadcrumbs(userUuid));
+        try {
+            dispatch(progressIndicatorActions.START_WORKING(uuid));
+            if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
+                // Load another users home projects
+                dispatch(finishLoadingProject(uuid));
+            } else if (userUuid !== uuid) {
+                await dispatch(finishLoadingProject(uuid));
+                const match = await loadGroupContentsResource({
+                    uuid,
+                    userUuid,
+                    services,
+                });
+                match({
+                    OWNED: async () => {
+                        await dispatch(activateSidePanelTreeItem(uuid));
+                        dispatch<any>(setSidePanelBreadcrumbs(uuid));
+                    },
+                    SHARED: async () => {
+                        await dispatch(activateSidePanelTreeItem(uuid));
+                        dispatch<any>(setSharedWithMeBreadcrumbs(uuid));
+                    },
+                    TRASHED: async () => {
+                        await dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
+                        dispatch<any>(setTrashBreadcrumbs(uuid));
+                        dispatch(setIsProjectPanelTrashed(true));
+                    },
+                });
+            } else {
+                await dispatch(finishLoadingProject(userUuid));
+                await dispatch(activateSidePanelTreeItem(userUuid));
+                dispatch<any>(setSidePanelBreadcrumbs(userUuid));
+            }
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING(uuid));
         }
     });
 
@@ -279,64 +288,63 @@ export const createProject = (data: projectCreateActions.ProjectCreateFormDialog
 };
 
 export const moveProject =
-    (data: MoveToFormDialogData, secondaryMoveKind: string = "") =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const checkedList = getState().multiselect.checkedList;
-        const uuidsToMove: string[] = selectedToArray(checkedList);
+    (data: MoveToFormDialogData, isSecondaryMove = false) =>
+        async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+            const checkedList = getState().multiselect.checkedList;
+            const uuidsToMove: string[] = data.fromContextMenu ? [data.uuid] : selectedToArray(checkedList);
 
-        //if no items in checkedlist && no items passed in, default to normal context menu behavior
-        if (!secondaryMoveKind.length && !uuidsToMove.length) uuidsToMove.push(data.uuid);
+            //if no items in checkedlist default to normal context menu behavior
+            if (!isSecondaryMove && !uuidsToMove.length) uuidsToMove.push(data.uuid);
 
-        const sourceUuid = getResource(data.uuid)(getState().resources)?.ownerUuid;
-        const destinationUuid = data.ownerUuid;
+            const sourceUuid = getResource(data.uuid)(getState().resources)?.ownerUuid;
+            const destinationUuid = data.ownerUuid;
 
-        const projectsToMove: MoveableResource[] = uuidsToMove
-            .map(uuid => getResource(uuid)(getState().resources) as MoveableResource)
-            .filter(resource => resource.kind === ResourceKind.PROJECT);
+            const projectsToMove: MoveableResource[] = uuidsToMove
+                .map(uuid => getResource(uuid)(getState().resources) as MoveableResource)
+                .filter(resource => resource.kind === ResourceKind.PROJECT);
 
-        for (const project of projectsToMove) {
-            await moveSingleProject(project);
-        }
+            for (const project of projectsToMove) {
+                await moveSingleProject(project);
+            }
 
-        if (!secondaryMoveKind.length) {
-            const kindsToMove: Set<string> = selectedToKindSet(checkedList);
-            kindsToMove.delete(ResourceKind.PROJECT);
+            //omly propagate if this call is the original
+            if (!isSecondaryMove) {
+                const kindsToMove: Set<string> = selectedToKindSet(checkedList);
+                kindsToMove.delete(ResourceKind.PROJECT);
 
-            kindsToMove.forEach(kind => {
-                secondaryMove[kind](data, kind)(dispatch, getState, services);
-                console.log(secondaryMove[kind]);
-            });
-        }
+                kindsToMove.forEach(kind => {
+                    secondaryMove[kind](data, true)(dispatch, getState, services);
+                });
+            }
 
-        async function moveSingleProject(project: MoveableResource) {
-            try {
-                const oldProject: MoveToFormDialogData = { name: project.name, uuid: project.uuid, ownerUuid: data.ownerUuid };
-                const oldOwnerUuid = oldProject ? oldProject.ownerUuid : "";
-                const movedProject = await dispatch<any>(projectMoveActions.moveProject(oldProject));
-                if (movedProject) {
+            async function moveSingleProject(project: MoveableResource) {
+                try {
+                    const oldProject: MoveToFormDialogData = { name: project.name, uuid: project.uuid, ownerUuid: data.ownerUuid };
+                    const oldOwnerUuid = oldProject ? oldProject.ownerUuid : "";
+                    const movedProject = await dispatch<any>(projectMoveActions.moveProject(oldProject));
+                    if (movedProject) {
+                        dispatch(
+                            snackbarActions.OPEN_SNACKBAR({
+                                message: "Project has been moved",
+                                hideDuration: 2000,
+                                kind: SnackbarKind.SUCCESS,
+                            })
+                        );
+                        await dispatch<any>(reloadProjectMatchingUuid([oldOwnerUuid, movedProject.ownerUuid, movedProject.uuid]));
+                    }
+                } catch (e) {
                     dispatch(
                         snackbarActions.OPEN_SNACKBAR({
-                            message: "Project has been moved",
+                            message: !!(project as any).frozenByUuid ? 'Could not move frozen project.' : e.message,
                             hideDuration: 2000,
-                            kind: SnackbarKind.SUCCESS,
+                            kind: SnackbarKind.ERROR,
                         })
                     );
-                    await dispatch<any>(reloadProjectMatchingUuid([oldOwnerUuid, movedProject.ownerUuid, movedProject.uuid]));
                 }
-            } catch (e) {
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: e.message,
-                        hideDuration: 2000,
-                        kind: SnackbarKind.ERROR,
-                    })
-                );
-                // }
             }
-        }
-        if (sourceUuid) await dispatch<any>(loadSidePanelTreeProjects(sourceUuid));
-        await dispatch<any>(loadSidePanelTreeProjects(destinationUuid));
-    };
+            if (sourceUuid) await dispatch<any>(loadSidePanelTreeProjects(sourceUuid));
+            await dispatch<any>(loadSidePanelTreeProjects(destinationUuid));
+        };
 
 export const updateProject = (data: projectUpdateActions.ProjectUpdateFormDialogData) => async (dispatch: Dispatch) => {
     const updatedProject = await dispatch<any>(projectUpdateActions.updateProject(data));
@@ -371,42 +379,47 @@ export const updateGroup = (data: projectUpdateActions.ProjectUpdateFormDialogDa
 export const loadCollection = (uuid: string) =>
     handleFirstTimeLoad(async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         const userUuid = getUserUuid(getState());
-        if (userUuid) {
-            const match = await loadGroupContentsResource({
-                uuid,
-                userUuid,
-                services,
-            });
-            let collection: CollectionResource | undefined;
-            let breadcrumbfunc:
-                | ((uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => Promise<void>)
-                | undefined;
-            let sidepanel: string | undefined;
-            match({
-                OWNED: thecollection => {
-                    collection = thecollection as CollectionResource;
-                    sidepanel = collection.ownerUuid;
-                    breadcrumbfunc = setSidePanelBreadcrumbs;
-                },
-                SHARED: thecollection => {
-                    collection = thecollection as CollectionResource;
-                    sidepanel = collection.ownerUuid;
-                    breadcrumbfunc = setSharedWithMeBreadcrumbs;
-                },
-                TRASHED: thecollection => {
-                    collection = thecollection as CollectionResource;
-                    sidepanel = SidePanelTreeCategory.TRASH;
-                    breadcrumbfunc = () => setTrashBreadcrumbs("");
-                },
-            });
-            if (collection && breadcrumbfunc && sidepanel) {
-                dispatch(updateResources([collection]));
-                await dispatch<any>(finishLoadingProject(collection.ownerUuid));
-                dispatch(collectionPanelActions.SET_COLLECTION(collection));
-                await dispatch(activateSidePanelTreeItem(sidepanel));
-                dispatch(breadcrumbfunc(collection.ownerUuid));
-                dispatch(loadCollectionPanel(collection.uuid));
+        try {
+            dispatch(progressIndicatorActions.START_WORKING(uuid));
+            if (userUuid) {
+                const match = await loadGroupContentsResource({
+                    uuid,
+                    userUuid,
+                    services,
+                });
+                let collection: CollectionResource | undefined;
+                let breadcrumbfunc:
+                    | ((uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => Promise<void>)
+                    | undefined;
+                let sidepanel: string | undefined;
+                match({
+                    OWNED: thecollection => {
+                        collection = thecollection as CollectionResource;
+                        sidepanel = collection.ownerUuid;
+                        breadcrumbfunc = setSidePanelBreadcrumbs;
+                    },
+                    SHARED: thecollection => {
+                        collection = thecollection as CollectionResource;
+                        sidepanel = collection.ownerUuid;
+                        breadcrumbfunc = setSharedWithMeBreadcrumbs;
+                    },
+                    TRASHED: thecollection => {
+                        collection = thecollection as CollectionResource;
+                        sidepanel = SidePanelTreeCategory.TRASH;
+                        breadcrumbfunc = () => setTrashBreadcrumbs("");
+                    },
+                });
+                if (collection && breadcrumbfunc && sidepanel) {
+                    dispatch(updateResources([collection]));
+                    await dispatch<any>(finishLoadingProject(collection.ownerUuid));
+                    dispatch(collectionPanelActions.SET_COLLECTION(collection));
+                    await dispatch(activateSidePanelTreeItem(sidepanel));
+                    dispatch(breadcrumbfunc(collection.ownerUuid));
+                    dispatch(loadCollectionPanel(collection.uuid));
+                }
             }
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING(uuid));
         }
     });
 
@@ -426,51 +439,88 @@ export const createCollection = (data: collectionCreateActions.CollectionCreateF
 };
 
 export const copyCollection = (data: CopyFormDialogData) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-    try {
-        const copyToProject = getResource(data.ownerUuid)(getState().resources);
-        const collection = await dispatch<any>(collectionCopyActions.copyCollection(data));
-        if (copyToProject && collection) {
-            dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
+    const checkedList = getState().multiselect.checkedList;
+    const uuidsToCopy: string[] = data.fromContextMenu ? [data.uuid] : selectedToArray(checkedList);
+
+    //if no items in checkedlist && no items passed in, default to normal context menu behavior
+    if (!uuidsToCopy.length) uuidsToCopy.push(data.uuid);
+
+    const collectionsToCopy: CollectionCopyResource[] = uuidsToCopy
+        .map(uuid => getResource(uuid)(getState().resources) as CollectionCopyResource)
+        .filter(resource => resource.kind === ResourceKind.COLLECTION);
+
+    for (const collection of collectionsToCopy) {
+        await copySingleCollection({ ...collection, ownerUuid: data.ownerUuid } as CollectionCopyResource);
+    }
+
+    async function copySingleCollection(copyToProject: CollectionCopyResource) {
+        const newName = data.fromContextMenu || collectionsToCopy.length === 1 ? data.name : `Copy of: ${copyToProject.name}`;
+        try {
+            const collection = await dispatch<any>(
+                collectionCopyActions.copyCollection({
+                    ...copyToProject,
+                    name: newName,
+                    fromContextMenu: collectionsToCopy.length === 1 ? true : data.fromContextMenu,
+                })
+            );
+            if (copyToProject && collection) {
+                await dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
+                dispatch(
+                    snackbarActions.OPEN_SNACKBAR({
+                        message: "Collection has been copied.",
+                        hideDuration: 3000,
+                        kind: SnackbarKind.SUCCESS,
+                        link: collection.ownerUuid,
+                    })
+                );
+                dispatch<any>(multiselectActions.deselectOne(copyToProject.uuid));
+            }
+        } catch (e) {
             dispatch(
                 snackbarActions.OPEN_SNACKBAR({
-                    message: "Collection has been copied.",
-                    hideDuration: 3000,
-                    kind: SnackbarKind.SUCCESS,
-                    link: collection.ownerUuid,
+                    message: e.message,
+                    hideDuration: 2000,
+                    kind: SnackbarKind.ERROR,
                 })
             );
         }
-    } catch (e) {
-        dispatch(
-            snackbarActions.OPEN_SNACKBAR({
-                message: e.message,
-                hideDuration: 2000,
-                kind: SnackbarKind.ERROR,
-            })
-        );
     }
+    dispatch(projectPanelActions.REQUEST_ITEMS());
 };
 
 export const moveCollection =
-    (data: MoveToFormDialogData, secondaryMoveKind: string = "") =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        console.log("MoveCollection?", data, secondaryMoveKind);
-        const itemsToMove: string[] = selectedToArray(getState().multiselect.checkedList);
-        //if no items in checkedlist, default to normal context menu behavior
-        if (!itemsToMove.length) itemsToMove.push(data.uuid);
-
-        for (const uuid of itemsToMove) {
-            await moveSingleCollection(uuid);
-        }
+    (data: MoveToFormDialogData, isSecondaryMove = false) =>
+        async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+            const checkedList = getState().multiselect.checkedList;
+            const uuidsToMove: string[] = data.fromContextMenu ? [data.uuid] : selectedToArray(checkedList);
+
+            //if no items in checkedlist && no items passed in, default to normal context menu behavior
+            if (!isSecondaryMove && !uuidsToMove.length) uuidsToMove.push(data.uuid);
+
+            const collectionsToMove: MoveableResource[] = uuidsToMove
+                .map(uuid => getResource(uuid)(getState().resources) as MoveableResource)
+                .filter(resource => resource.kind === ResourceKind.COLLECTION);
+
+            for (const collection of collectionsToMove) {
+                await moveSingleCollection(collection);
+            }
+
+            //omly propagate if this call is the original
+            if (!isSecondaryMove) {
+                const kindsToMove: Set<string> = selectedToKindSet(checkedList);
+                kindsToMove.delete(ResourceKind.COLLECTION);
+
+                kindsToMove.forEach(kind => {
+                    secondaryMove[kind](data, true)(dispatch, getState, services);
+                });
+            }
 
-        async function moveSingleCollection(uuid: string) {
-            const originalItem = getResource(uuid)(getState().resources) as Resource & { name: string };
-            if (originalItem.kind === ResourceKind.COLLECTION) {
+            async function moveSingleCollection(collection: MoveableResource) {
                 try {
-                    const oldCollection: MoveToFormDialogData = { name: originalItem.name, uuid: originalItem.uuid, ownerUuid: data.ownerUuid };
-                    const collection = await dispatch<any>(collectionMoveActions.moveCollection(oldCollection));
-                    dispatch<any>(updateResources([collection]));
-                    dispatch<any>(reloadProjectMatchingUuid([collection.ownerUuid]));
+                    const oldCollection: MoveToFormDialogData = { name: collection.name, uuid: collection.uuid, ownerUuid: data.ownerUuid };
+                    const movedCollection = await dispatch<any>(collectionMoveActions.moveCollection(oldCollection));
+                    dispatch<any>(updateResources([movedCollection]));
+                    dispatch<any>(reloadProjectMatchingUuid([movedCollection.ownerUuid]));
                     dispatch(
                         snackbarActions.OPEN_SNACKBAR({
                             message: "Collection has been moved.",
@@ -488,18 +538,22 @@ export const moveCollection =
                     );
                 }
             }
-        }
-    };
+        };
 
 export const loadProcess = (uuid: string) =>
     handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState) => {
-        dispatch<any>(loadProcessPanel(uuid));
-        const process = await dispatch<any>(processesActions.loadProcess(uuid));
-        if (process) {
-            await dispatch<any>(finishLoadingProject(process.containerRequest.ownerUuid));
-            await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
-            dispatch<any>(setProcessBreadcrumbs(uuid));
-            dispatch<any>(loadDetailsPanel(uuid));
+        try {
+            dispatch(progressIndicatorActions.START_WORKING(uuid));
+            dispatch<any>(loadProcessPanel(uuid));
+            const process = await dispatch<any>(processesActions.loadProcess(uuid));
+            if (process) {
+                await dispatch<any>(finishLoadingProject(process.containerRequest.ownerUuid));
+                await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
+                dispatch<any>(setProcessBreadcrumbs(uuid));
+                dispatch<any>(loadDetailsPanel(uuid));
+            }
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING(uuid));
         }
     });
 
@@ -525,7 +579,7 @@ export const loadRegisteredWorkflow = (uuid: string) =>
                     workflow = theworkflow as WorkflowResource;
                     breadcrumbfunc = setSharedWithMeBreadcrumbs;
                 },
-                TRASHED: () => {},
+                TRASHED: () => { },
             });
             if (workflow && breadcrumbfunc) {
                 dispatch(updateResources([workflow]));
@@ -561,42 +615,57 @@ export const updateProcess = (data: processUpdateActions.ProcessUpdateFormDialog
     }
 };
 
-export const moveProcess = (data: MoveToFormDialogData) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-    const itemsToMove: string[] = selectedToArray(getState().multiselect.checkedList);
-    //if no items in checkedlist, default to normal context menu behavior
-    if (!itemsToMove.length) itemsToMove.push(data.uuid);
+export const moveProcess =
+    (data: MoveToFormDialogData, isSecondaryMove = false) =>
+        async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+            const checkedList = getState().multiselect.checkedList;
+            const uuidsToMove: string[] = data.fromContextMenu ? [data.uuid] : selectedToArray(checkedList);
 
-    for (const uuid of itemsToMove) {
-        await moveSingleProcess(uuid);
-    }
+            //if no items in checkedlist && no items passed in, default to normal context menu behavior
+            if (!isSecondaryMove && !uuidsToMove.length) uuidsToMove.push(data.uuid);
 
-    async function moveSingleProcess(uuid: string) {
-        const originalItem = getResource(uuid)(getState().resources) as Resource & { name: string };
-        if (originalItem.kind === ResourceKind.PROCESS) {
-            try {
-                const oldProcess: MoveToFormDialogData = { name: originalItem.name, uuid: originalItem.uuid, ownerUuid: data.ownerUuid };
-                const process = await dispatch<any>(processMoveActions.moveProcess(oldProcess));
-                dispatch<any>(updateResources([process]));
-                dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: "Process has been moved.",
-                        hideDuration: 2000,
-                        kind: SnackbarKind.SUCCESS,
-                    })
-                );
-            } catch (e) {
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: e.message,
-                        hideDuration: 2000,
-                        kind: SnackbarKind.ERROR,
-                    })
-                );
+            const processesToMove: MoveableResource[] = uuidsToMove
+                .map(uuid => getResource(uuid)(getState().resources) as MoveableResource)
+                .filter(resource => resource.kind === ResourceKind.PROCESS);
+
+            for (const process of processesToMove) {
+                await moveSingleProcess(process);
             }
-        }
-    }
-};
+
+            //omly propagate if this call is the original
+            if (!isSecondaryMove) {
+                const kindsToMove: Set<string> = selectedToKindSet(checkedList);
+                kindsToMove.delete(ResourceKind.PROCESS);
+
+                kindsToMove.forEach(kind => {
+                    secondaryMove[kind](data, true)(dispatch, getState, services);
+                });
+            }
+
+            async function moveSingleProcess(process: MoveableResource) {
+                try {
+                    const oldProcess: MoveToFormDialogData = { name: process.name, uuid: process.uuid, ownerUuid: data.ownerUuid };
+                    const movedProcess = await dispatch<any>(processMoveActions.moveProcess(oldProcess));
+                    dispatch<any>(updateResources([movedProcess]));
+                    dispatch<any>(reloadProjectMatchingUuid([movedProcess.ownerUuid]));
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: "Process has been moved.",
+                            hideDuration: 2000,
+                            kind: SnackbarKind.SUCCESS,
+                        })
+                    );
+                } catch (e) {
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: e.message,
+                            hideDuration: 2000,
+                            kind: SnackbarKind.ERROR,
+                        })
+                    );
+                }
+            }
+        };
 
 export const copyProcess = (data: CopyFormDialogData) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
     try {
@@ -735,7 +804,6 @@ export const loadGroupDetailsPanel = (groupUuid: string) =>
 
 const finishLoadingProject = (project: GroupContentsResource | string) => async (dispatch: Dispatch<any>) => {
     const uuid = typeof project === "string" ? project : project.uuid;
-    dispatch(openProjectPanel(uuid));
     dispatch(loadDetailsPanel(uuid));
     if (typeof project !== "string") {
         dispatch(updateResources([project]));
@@ -785,11 +853,13 @@ const groupContentsHandlers = unionize(groupContentsHandlersRecord);
 
 type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;
 
+type CollectionCopyResource = Resource & { name: string; fromContextMenu: boolean };
+
 type MoveableResource = Resource & { name: string };
 
 type MoveFunc = (
     data: MoveToFormDialogData,
-    secondaryMoveKind?: string
+    isSecondaryMove?: boolean
 ) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => Promise<void>;
 
 const secondaryMove: Record<string, MoveFunc> = {