21077: Add explicit START/STOP_WORKING to loading functions 21077-background-refresh
authorPeter Amstutz <peter.amstutz@curii.com>
Thu, 19 Oct 2023 14:31:32 +0000 (10:31 -0400)
committerPeter Amstutz <peter.amstutz@curii.com>
Thu, 19 Oct 2023 14:31:32 +0000 (10:31 -0400)
Previously, progressFn meant that any time there was an active API
request, it would spin the progress bar.  To allow for background
requests (where we don't want it to spin because it is
distracting/confusing to the user), we need to remove that default
behavior.  As a result, to provide feedback that something is
happening, functions need to explicitly set START_WORKING and
STOP_WORKING.  This was implemented inconsistently, because the
default processFn behavior tended to cover a lot of cases.

This commit adds START/STOP to all the major panel loading functions
in the UI.  This provides better user feedback overall, because the
spinner now more consistently covers the entire loading
process (across multiple API calls and async behavior), instead of
just individual API calls.

Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

src/store/breadcrumbs/breadcrumbs-actions.ts
src/store/collection-panel/collection-panel-action.ts
src/store/group-details-panel/group-details-panel-members-middleware-service.ts
src/store/link-panel/link-panel-middleware-service.ts
src/store/process-panel/process-panel-actions.ts
src/store/processes/process.ts
src/store/users/user-panel-middleware-service.ts
src/store/virtual-machines/virtual-machines-actions.ts
src/store/workbench/workbench-actions.ts

index 9c9baf08ad76977d708d41107acf1873fee6b440..9aebeb904c64115e574624163718d2fea43bcb82 100644 (file)
@@ -24,6 +24,7 @@ import { CollectionIcon, IconType, ProcessIcon, ProjectIcon, WorkflowIcon } from
 import { CollectionResource } from 'models/collection';
 import { getSidePanelIcon } from 'views-components/side-panel-tree/side-panel-tree';
 import { WorkflowResource } from 'models/workflow';
+import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 
 export const BREADCRUMBS = 'breadcrumbs';
 
@@ -57,60 +58,65 @@ const resourceToBreadcrumb = (resource: CollectionResource | ContainerRequestRes
 
 export const setSidePanelBreadcrumbs = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const ancestors = await services.ancestorsService.ancestors(uuid, '');
-        dispatch(updateResources(ancestors));
+        try {
+            dispatch(progressIndicatorActions.START_WORKING(uuid + "-breadcrumbs"));
+            const ancestors = await services.ancestorsService.ancestors(uuid, '');
+            dispatch(updateResources(ancestors));
 
-        let breadcrumbs: Breadcrumb[] = [];
-        const { collectionPanel: { item } } = getState();
+            let breadcrumbs: Breadcrumb[] = [];
+            const { collectionPanel: { item } } = getState();
 
-        const path = getState().router.location!.pathname;
-        const currentUuid = path.split('/')[2];
-        const uuidKind = extractUuidKind(currentUuid);
-        const rootUuid = getUserUuid(getState());
+            const path = getState().router.location!.pathname;
+            const currentUuid = path.split('/')[2];
+            const uuidKind = extractUuidKind(currentUuid);
+            const rootUuid = getUserUuid(getState());
 
-        if (ancestors.find(ancestor => ancestor.uuid === rootUuid)) {
-            // Handle home project uuid root
-            breadcrumbs.push({
-                label: SidePanelTreeCategory.PROJECTS,
-                uuid: SidePanelTreeCategory.PROJECTS,
-                icon: getSidePanelIcon(SidePanelTreeCategory.PROJECTS)
-            });
-        } else if (Object.values(SidePanelTreeCategory).includes(uuid as SidePanelTreeCategory)) {
-            // Handle SidePanelTreeCategory root
-            breadcrumbs.push({
-                label: uuid,
-                uuid: uuid,
-                icon: getSidePanelIcon(uuid)
-            });
-        }
+            if (ancestors.find(ancestor => ancestor.uuid === rootUuid)) {
+                // Handle home project uuid root
+                breadcrumbs.push({
+                    label: SidePanelTreeCategory.PROJECTS,
+                    uuid: SidePanelTreeCategory.PROJECTS,
+                    icon: getSidePanelIcon(SidePanelTreeCategory.PROJECTS)
+                });
+            } else if (Object.values(SidePanelTreeCategory).includes(uuid as SidePanelTreeCategory)) {
+                // Handle SidePanelTreeCategory root
+                breadcrumbs.push({
+                    label: uuid,
+                    uuid: uuid,
+                    icon: getSidePanelIcon(uuid)
+                });
+            }
 
-        breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
-            ancestor.kind === ResourceKind.GROUP
-                ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
-                : breadcrumbs,
-            breadcrumbs);
+            breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
+                ancestor.kind === ResourceKind.GROUP
+                    ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
+                    : breadcrumbs,
+                breadcrumbs);
 
-        if (uuidKind === ResourceKind.COLLECTION) {
-            const collectionItem = item ? item : await services.collectionService.get(currentUuid);
-            const parentProcessItem = await getCollectionParent(collectionItem)(services);
-            if (parentProcessItem) {
-                const mainProcessItem = await getProcessParent(parentProcessItem)(services);
-                mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
-                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
-            }
-            dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
-        } else if (uuidKind === ResourceKind.PROCESS) {
-            const processItem = await services.containerRequestService.get(currentUuid);
-            const parentProcessItem = await getProcessParent(processItem)(services);
-            if (parentProcessItem) {
-                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+            if (uuidKind === ResourceKind.COLLECTION) {
+                const collectionItem = item ? item : await services.collectionService.get(currentUuid);
+                const parentProcessItem = await getCollectionParent(collectionItem)(services);
+                if (parentProcessItem) {
+                    const mainProcessItem = await getProcessParent(parentProcessItem)(services);
+                    mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
+                    breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+                }
+                dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
+            } else if (uuidKind === ResourceKind.PROCESS) {
+                const processItem = await services.containerRequestService.get(currentUuid);
+                const parentProcessItem = await getProcessParent(processItem)(services);
+                if (parentProcessItem) {
+                    breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+                }
+                dispatch(setBreadcrumbs(breadcrumbs, processItem));
+            } else if (uuidKind === ResourceKind.WORKFLOW) {
+                const workflowItem = await services.workflowService.get(currentUuid);
+                dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
             }
-            dispatch(setBreadcrumbs(breadcrumbs, processItem));
-        } else if (uuidKind === ResourceKind.WORKFLOW) {
-            const workflowItem = await services.workflowService.get(currentUuid);
-            dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
+            dispatch(setBreadcrumbs(breadcrumbs));
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
         }
-        dispatch(setBreadcrumbs(breadcrumbs));
     };
 
 export const setSharedWithMeBreadcrumbs = (uuid: string) =>
@@ -121,45 +127,50 @@ export const setTrashBreadcrumbs = (uuid: string) =>
 
 export const setCategoryBreadcrumbs = (uuid: string, category: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const ancestors = await services.ancestorsService.ancestors(uuid, '');
-        dispatch(updateResources(ancestors));
-        const initialBreadcrumbs: Breadcrumb[] = [
-            {
-                label: category,
-                uuid: category,
-                icon: getSidePanelIcon(category)
-            }
-        ];
-        const { collectionPanel: { item } } = getState();
-        const path = getState().router.location!.pathname;
-        const currentUuid = path.split('/')[2];
-        const uuidKind = extractUuidKind(currentUuid);
-        let breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
-            ancestor.kind === ResourceKind.GROUP
-                ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
-                : breadcrumbs,
-            initialBreadcrumbs);
-        if (uuidKind === ResourceKind.COLLECTION) {
-            const collectionItem = item ? item : await services.collectionService.get(currentUuid);
-            const parentProcessItem = await getCollectionParent(collectionItem)(services);
-            if (parentProcessItem) {
-                const mainProcessItem = await getProcessParent(parentProcessItem)(services);
-                mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
-                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
-            }
-            dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
-        } else if (uuidKind === ResourceKind.PROCESS) {
-            const processItem = await services.containerRequestService.get(currentUuid);
-            const parentProcessItem = await getProcessParent(processItem)(services);
-            if (parentProcessItem) {
-                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+        try {
+            dispatch(progressIndicatorActions.START_WORKING(uuid + "-breadcrumbs"));
+            const ancestors = await services.ancestorsService.ancestors(uuid, '');
+            dispatch(updateResources(ancestors));
+            const initialBreadcrumbs: Breadcrumb[] = [
+                {
+                    label: category,
+                    uuid: category,
+                    icon: getSidePanelIcon(category)
+                }
+            ];
+            const { collectionPanel: { item } } = getState();
+            const path = getState().router.location!.pathname;
+            const currentUuid = path.split('/')[2];
+            const uuidKind = extractUuidKind(currentUuid);
+            let breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
+                ancestor.kind === ResourceKind.GROUP
+                    ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
+                    : breadcrumbs,
+                initialBreadcrumbs);
+            if (uuidKind === ResourceKind.COLLECTION) {
+                const collectionItem = item ? item : await services.collectionService.get(currentUuid);
+                const parentProcessItem = await getCollectionParent(collectionItem)(services);
+                if (parentProcessItem) {
+                    const mainProcessItem = await getProcessParent(parentProcessItem)(services);
+                    mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
+                    breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+                }
+                dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
+            } else if (uuidKind === ResourceKind.PROCESS) {
+                const processItem = await services.containerRequestService.get(currentUuid);
+                const parentProcessItem = await getProcessParent(processItem)(services);
+                if (parentProcessItem) {
+                    breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+                }
+                dispatch(setBreadcrumbs(breadcrumbs, processItem));
+            } else if (uuidKind === ResourceKind.WORKFLOW) {
+                const workflowItem = await services.workflowService.get(currentUuid);
+                dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
             }
-            dispatch(setBreadcrumbs(breadcrumbs, processItem));
-        } else if (uuidKind === ResourceKind.WORKFLOW) {
-            const workflowItem = await services.workflowService.get(currentUuid);
-            dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
+            dispatch(setBreadcrumbs(breadcrumbs));
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
         }
-        dispatch(setBreadcrumbs(breadcrumbs));
     };
 
 const getProcessParent = (childProcess: ContainerRequestResource) =>
index 7bab86320da1e00c2a3f2a1706b824722c87e14c..49573215af9224d55942d2dc2e33c8c177d017e5 100644 (file)
@@ -12,6 +12,7 @@ import { unionize, ofType, UnionOf } from 'common/unionize';
 import { SnackbarKind } from 'store/snackbar/snackbar-actions';
 import { navigateTo } from 'store/navigation/navigation-action';
 import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
+import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 
 export const collectionPanelActions = unionize({
     SET_COLLECTION: ofType<CollectionResource>(),
@@ -24,9 +25,14 @@ export const loadCollectionPanel = (uuid: string, forceReload = false) =>
         const { collectionPanel: { item } } = getState();
         let collection: CollectionResource | null = null;
         if (!item || item.uuid !== uuid || forceReload) {
-            collection = await services.collectionService.get(uuid);
-            dispatch(collectionPanelActions.SET_COLLECTION(collection));
-            dispatch(resourcesActions.SET_RESOURCES([collection]));
+            try {
+                dispatch(progressIndicatorActions.START_WORKING(uuid + "-panel"));
+                collection = await services.collectionService.get(uuid);
+                dispatch(collectionPanelActions.SET_COLLECTION(collection));
+                dispatch(resourcesActions.SET_RESOURCES([collection]));
+            } finally {
+                dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-panel"));
+            }
         } else {
             collection = item;
         }
index 3a58927aac7cd3da6a3c41d13aef2fa3161dc82a..507b4eb30fb2aaa1fdd5d38add18f7b56b03c32a 100644 (file)
@@ -13,6 +13,7 @@ import { updateResources } from 'store/resources/resources-actions';
 import { getCurrentGroupDetailsPanelUuid, GroupMembersPanelActions } from 'store/group-details-panel/group-details-panel-actions';
 import { LinkClass } from 'models/link';
 import { ResourceKind } from 'models/resource';
+import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
 
 export class GroupDetailsPanelMembersMiddlewareService extends DataExplorerMiddlewareService {
 
@@ -28,6 +29,7 @@ export class GroupDetailsPanelMembersMiddlewareService extends DataExplorerMiddl
             return;
         } else {
             try {
+                api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
                 const groupResource = await this.services.groupsService.get(groupUuid);
                 api.dispatch(updateResources([groupResource]));
 
@@ -65,6 +67,8 @@ export class GroupDetailsPanelMembersMiddlewareService extends DataExplorerMiddl
                 api.dispatch(updateResources(projectsIn.items));
             } catch (e) {
                 api.dispatch(couldNotFetchGroupDetailsContents());
+            } finally {
+                api.dispatch(progressIndicatorActions.STOP_WORKING(this.getId()));
             }
         }
     }
index 87bcba0cbc24cf182db6d1b72feb809c4d56474e..cc6ea8cf389ebdd09573630f419477920792997a 100644 (file)
@@ -12,6 +12,7 @@ import { updateResources } from 'store/resources/resources-actions';
 import { ListResults } from 'services/common-service/common-service';
 import { LinkResource } from 'models/link';
 import { linkPanelActions } from 'store/link-panel/link-panel-actions';
+import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 
 export class LinkMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -22,11 +23,14 @@ export class LinkMiddlewareService extends DataExplorerMiddlewareService {
         const state = api.getState();
         const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
         try {
+            api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
             const response = await this.services.linkService.list(getParams(dataExplorer));
             api.dispatch(updateResources(response.items));
             api.dispatch(setItems(response));
         } catch {
             api.dispatch(couldNotFetchLinks());
+        } finally {
+            api.dispatch(progressIndicatorActions.STOP_WORKING(this.getId()));
         }
     }
 }
index 720c612640c96212eb80790d7bf2d3fea5a70143..03e36aac98fb837752c932df4a72c913cea5cc6c 100644 (file)
@@ -151,7 +151,7 @@ export const updateOutputParams = () => async (dispatch: Dispatch<any>, getState
     const outputDefinitions = getState().processPanel.outputDefinitions;
     const outputRaw = getState().processPanel.outputRaw;
 
-    if (outputRaw !== null && outputRaw.rawOutputs) {
+    if (outputRaw && outputRaw.rawOutputs) {
         dispatch<ProcessPanelAction>(
             processPanelActions.SET_OUTPUT_PARAMS(formatOutputData(outputDefinitions, outputRaw.rawOutputs, outputRaw.pdh, getState().auth))
         );
index 1063d0bd487f87c33c0ec13f6d84695db3ecad35..a31fd9eac8b12b9cf8eea5d3956d588d47f8cc79 100644 (file)
@@ -134,8 +134,10 @@ export const getProcessStatus = ({ containerRequest, container }: Process): Proc
 
         case containerRequest.state === ContainerRequestState.FINAL &&
             container?.state === ContainerState.RUNNING:
-            // It's right about to be completed but we haven't
-            // gotten the updated container record yet
+            // It is about to be completed but we haven't
+            // gotten the updated container record yet,
+            // if we don't catch this and show it as "Running"
+            // it will flicker "Cancelled" briefly
             return ProcessStatus.RUNNING;
 
         case containerRequest.state === ContainerRequestState.FINAL &&
index e965cd00580f85ce20944d12d760f089b7984aec..4dae207222f6f5a5d25cdc852474105b4fffd868 100644 (file)
@@ -19,6 +19,7 @@ import { UserResource } from 'models/user';
 import { UserPanelColumnNames } from 'views/user-panel/user-panel';
 import { BuiltinGroups, getBuiltinGroupUuid } from 'models/group';
 import { LinkClass } from 'models/link';
+import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 
 export class UserMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -29,6 +30,7 @@ export class UserMiddlewareService extends DataExplorerMiddlewareService {
         const state = api.getState();
         const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
         try {
+            api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
             const users = await this.services.userService.list(getParams(dataExplorer));
             api.dispatch(updateResources(users.items));
             api.dispatch(setItems(users));
@@ -44,6 +46,8 @@ export class UserMiddlewareService extends DataExplorerMiddlewareService {
             api.dispatch(updateResources(allUserMemberships.items));
         } catch {
             api.dispatch(couldNotFetchUsers());
+        } finally {
+            api.dispatch(progressIndicatorActions.STOP_WORKING(this.getId()));
         }
     }
 }
index bd07efb6409f3f09368b10eaf60c479234257bdb..12172e7fe32d5739a0c78fee6cdb13a4b07cb10a 100644 (file)
@@ -19,6 +19,7 @@ import { deleteResources, updateResources } from 'store/resources/resources-acti
 import { Participant } from "views-components/sharing-dialog/participant-select";
 import { initialize, reset } from "redux-form";
 import { getUserDisplayName, UserResource } from "models/user";
+import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
 
 export const virtualMachinesActions = unionize({
     SET_REQUESTED_DATE: ofType<string>(),
@@ -72,50 +73,61 @@ const loadRequestedDate = () =>
 
 export const loadVirtualMachinesAdminData = () =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch<any>(loadRequestedDate());
-
-        const virtualMachines = await services.virtualMachineService.list();
-        dispatch(updateResources(virtualMachines.items));
-        dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
-
-
-        const logins = await services.permissionService.list({
-            filters: new FilterBuilder()
-            .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
-            .addEqual('name', PermissionLevel.CAN_LOGIN)
-            .getFilters(),
-            limit: 1000
-        });
-        dispatch(updateResources(logins.items));
-        dispatch(virtualMachinesActions.SET_LINKS(logins));
-
-        const users = await services.userService.list({
-            filters: new FilterBuilder()
-            .addIn('uuid', logins.items.map(item => item.tailUuid))
-            .getFilters(),
-            count: "none", // Necessary for federated queries
-            limit: 1000
-        });
-        dispatch(updateResources(users.items));
-
-        const getAllLogins = await services.virtualMachineService.getAllLogins();
-        dispatch(virtualMachinesActions.SET_LOGINS(getAllLogins));
+        try {
+            dispatch(progressIndicatorActions.START_WORKING("virtual-machines-admin"));
+            dispatch<any>(loadRequestedDate());
+
+            const virtualMachines = await services.virtualMachineService.list();
+            dispatch(updateResources(virtualMachines.items));
+            dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
+
+
+            const logins = await services.permissionService.list({
+                filters: new FilterBuilder()
+                    .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
+                    .addEqual('name', PermissionLevel.CAN_LOGIN)
+                    .getFilters(),
+                limit: 1000
+            });
+            dispatch(updateResources(logins.items));
+            dispatch(virtualMachinesActions.SET_LINKS(logins));
+
+            const users = await services.userService.list({
+                filters: new FilterBuilder()
+                    .addIn('uuid', logins.items.map(item => item.tailUuid))
+                    .getFilters(),
+                count: "none", // Necessary for federated queries
+                limit: 1000
+            });
+            dispatch(updateResources(users.items));
+
+            const getAllLogins = await services.virtualMachineService.getAllLogins();
+            dispatch(virtualMachinesActions.SET_LOGINS(getAllLogins));
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING("virtual-machines-admin"));
+        }
     };
 
 export const loadVirtualMachinesUserData = () =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch<any>(loadRequestedDate());
-        const user = getState().auth.user;
-        const virtualMachines = await services.virtualMachineService.list();
-        const virtualMachinesUuids = virtualMachines.items.map(it => it.uuid);
-        const links = await services.linkService.list({
-            filters: new FilterBuilder()
-                .addIn("head_uuid", virtualMachinesUuids)
-                .addEqual("tail_uuid", user?.uuid)
-                .getFilters()
-        });
-        dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
-        dispatch(virtualMachinesActions.SET_LINKS(links));
+        try {
+            dispatch(progressIndicatorActions.START_WORKING("virtual-machines-user"));
+
+            dispatch<any>(loadRequestedDate());
+            const user = getState().auth.user;
+            const virtualMachines = await services.virtualMachineService.list();
+            const virtualMachinesUuids = virtualMachines.items.map(it => it.uuid);
+            const links = await services.linkService.list({
+                filters: new FilterBuilder()
+                    .addIn("head_uuid", virtualMachinesUuids)
+                    .addEqual("tail_uuid", user?.uuid)
+                    .getFilters()
+            });
+            dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
+            dispatch(virtualMachinesActions.SET_LINKS(links));
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING("virtual-machines-user"));
+        }
     };
 
 export const openAddVirtualMachineLoginDialog = (vmUuid: string) =>
@@ -125,17 +137,17 @@ export const openAddVirtualMachineLoginDialog = (vmUuid: string) =>
         dispatch(updateResources(virtualMachines.items));
         const logins = await services.permissionService.list({
             filters: new FilterBuilder()
-            .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
-            .addEqual('name', PermissionLevel.CAN_LOGIN)
-            .getFilters()
+                .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
+                .addEqual('name', PermissionLevel.CAN_LOGIN)
+                .getFilters()
         });
         dispatch(updateResources(logins.items));
 
         dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
-                [VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: vmUuid,
-                [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: [],
-            }));
-        dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {excludedParticipants: logins.items.map(it => it.tailUuid)}} ));
+            [VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: vmUuid,
+            [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: [],
+        }));
+        dispatch(dialogActions.OPEN_DIALOG({ id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: { excludedParticipants: logins.items.map(it => it.tailUuid) } }));
     }
 
 export const openEditVirtualMachineLoginDialog = (permissionUuid: string) =>
@@ -143,11 +155,11 @@ export const openEditVirtualMachineLoginDialog = (permissionUuid: string) =>
         const login = await services.permissionService.get(permissionUuid);
         const user = await services.userService.get(login.tailUuid);
         dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
-                [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: permissionUuid,
-                [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: {name: getUserDisplayName(user, true, true), uuid: login.tailUuid},
-                [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: login.properties.groups,
-            }));
-        dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {updating: true}} ));
+            [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: permissionUuid,
+            [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: { name: getUserDisplayName(user, true, true), uuid: login.tailUuid },
+            [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: login.properties.groups,
+        }));
+        dispatch(dialogActions.OPEN_DIALOG({ id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: { updating: true } }));
     }
 
 export interface AddLoginFormData {
@@ -158,15 +170,15 @@ export interface AddLoginFormData {
 }
 
 
-export const addUpdateVirtualMachineLogin = ({uuid, vmUuid, user, groups}: AddLoginFormData) =>
+export const addUpdateVirtualMachineLogin = ({ uuid, vmUuid, user, groups }: AddLoginFormData) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         let userResource: UserResource | undefined = undefined;
         try {
             // Get user
             userResource = await services.userService.get(user.uuid, false);
         } catch (e) {
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Failed to get user details.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
-                return;
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Failed to get user details.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
+            return;
         }
         try {
             if (uuid) {
index 956fa3d9a24a05379f45b1ef0c697b6097dea4f3..b03400d5ae28c7abc55b86ac5e0f01e99668e95a 100644 (file)
@@ -228,35 +228,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));
         }
     });
 
@@ -277,62 +282,62 @@ export const createProject = (data: projectCreateActions.ProjectCreateFormDialog
 
 export const moveProject =
     (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);
+        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 default to normal context menu behavior
-        if (!isSecondaryMove && !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);
+            }
 
-        //omly propagate if this call is the original
-        if (!isSecondaryMove) {
-            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, true)(dispatch, getState, services);
-            });
-        }
+                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: 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));
@@ -367,42 +372,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));
         }
     });
 
@@ -473,65 +483,70 @@ export const copyCollection = (data: CopyFormDialogData) => async (dispatch: Dis
 
 export const moveCollection =
     (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);
+        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);
+            //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);
+            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);
-        }
+            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);
+            //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);
-            });
-        }
+                kindsToMove.forEach(kind => {
+                    secondaryMove[kind](data, true)(dispatch, getState, services);
+                });
+            }
 
-        async function moveSingleCollection(collection: MoveableResource) {
-            try {
-                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.",
-                        hideDuration: 2000,
-                        kind: SnackbarKind.SUCCESS,
-                    })
-                );
-            } catch (e) {
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: e.message,
-                        hideDuration: 2000,
-                        kind: SnackbarKind.ERROR,
-                    })
-                );
+            async function moveSingleCollection(collection: MoveableResource) {
+                try {
+                    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.",
+                            hideDuration: 2000,
+                            kind: SnackbarKind.SUCCESS,
+                        })
+                    );
+                } catch (e) {
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: e.message,
+                            hideDuration: 2000,
+                            kind: SnackbarKind.ERROR,
+                        })
+                    );
+                }
             }
-        }
-    };
+        };
 
 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));
         }
     });
 
@@ -557,7 +572,7 @@ export const loadRegisteredWorkflow = (uuid: string) =>
                     workflow = theworkflow as WorkflowResource;
                     breadcrumbfunc = setSharedWithMeBreadcrumbs;
                 },
-                TRASHED: () => {},
+                TRASHED: () => { },
             });
             if (workflow && breadcrumbfunc) {
                 dispatch(updateResources([workflow]));
@@ -595,55 +610,55 @@ export const updateProcess = (data: processUpdateActions.ProcessUpdateFormDialog
 
 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);
+        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);
+            //if no items in checkedlist && no items passed in, default to normal context menu behavior
+            if (!isSecondaryMove && !uuidsToMove.length) uuidsToMove.push(data.uuid);
 
-        const processesToMove: MoveableResource[] = uuidsToMove
-            .map(uuid => getResource(uuid)(getState().resources) as MoveableResource)
-            .filter(resource => resource.kind === ResourceKind.PROCESS);
+            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);
-        }
+            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);
+            //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);
-            });
-        }
+                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,
-                    })
-                );
+            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 {