19900: Catch exceptions loading process container request
[arvados-workbench2.git] / src / store / workbench / workbench-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { Dispatch } from 'redux';
6 import { RootState } from 'store/store';
7 import { getUserUuid } from 'common/getuser';
8 import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
9 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
10 import {
11   favoritePanelActions,
12   loadFavoritePanel,
13 } from 'store/favorite-panel/favorite-panel-action';
14 import {
15   getProjectPanelCurrentUuid,
16   openProjectPanel,
17   projectPanelActions,
18   setIsProjectPanelTrashed,
19 } from 'store/project-panel/project-panel-action';
20 import {
21   activateSidePanelTreeItem,
22   initSidePanelTree,
23   loadSidePanelTreeProjects,
24   SidePanelTreeCategory,
25 } from 'store/side-panel-tree/side-panel-tree-actions';
26 import { updateResources } from 'store/resources/resources-actions';
27 import { projectPanelColumns } from 'views/project-panel/project-panel';
28 import { favoritePanelColumns } from 'views/favorite-panel/favorite-panel';
29 import { matchRootRoute } from 'routes/routes';
30 import {
31   setBreadcrumbs,
32   setGroupDetailsBreadcrumbs,
33   setGroupsBreadcrumbs,
34   setProcessBreadcrumbs,
35   setSharedWithMeBreadcrumbs,
36   setSidePanelBreadcrumbs,
37   setTrashBreadcrumbs,
38   setUsersBreadcrumbs,
39   setMyAccountBreadcrumbs,
40   setUserProfileBreadcrumbs,
41 } from 'store/breadcrumbs/breadcrumbs-actions';
42 import {
43   navigateTo,
44   navigateToRootProject,
45 } from 'store/navigation/navigation-action';
46 import { MoveToFormDialogData } from 'store/move-to-dialog/move-to-dialog';
47 import { ServiceRepository } from 'services/services';
48 import { getResource } from 'store/resources/resources';
49 import * as projectCreateActions from 'store/projects/project-create-actions';
50 import * as projectMoveActions from 'store/projects/project-move-actions';
51 import * as projectUpdateActions from 'store/projects/project-update-actions';
52 import * as collectionCreateActions from 'store/collections/collection-create-actions';
53 import * as collectionCopyActions from 'store/collections/collection-copy-actions';
54 import * as collectionMoveActions from 'store/collections/collection-move-actions';
55 import * as processesActions from 'store/processes/processes-actions';
56 import * as processMoveActions from 'store/processes/process-move-actions';
57 import * as processUpdateActions from 'store/processes/process-update-actions';
58 import * as processCopyActions from 'store/processes/process-copy-actions';
59 import { trashPanelColumns } from 'views/trash-panel/trash-panel';
60 import {
61   loadTrashPanel,
62   trashPanelActions,
63 } from 'store/trash-panel/trash-panel-action';
64 import { loadProcessPanel } from 'store/process-panel/process-panel-actions';
65 import {
66   loadSharedWithMePanel,
67   sharedWithMePanelActions,
68 } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
69 import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog';
70 import { workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions';
71 import { loadSshKeysPanel } from 'store/auth/auth-action-ssh';
72 import {
73   loadLinkAccountPanel,
74   linkAccountPanelActions,
75 } from 'store/link-account-panel/link-account-panel-actions';
76 import { loadSiteManagerPanel } from 'store/auth/auth-action-session';
77 import { workflowPanelColumns } from 'views/workflow-panel/workflow-panel-view';
78 import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
79 import { getProgressIndicator } from 'store/progress-indicator/progress-indicator-reducer';
80 import { extractUuidKind, ResourceKind } from 'models/resource';
81 import { FilterBuilder } from 'services/api/filter-builder';
82 import { GroupContentsResource } from 'services/groups-service/groups-service';
83 import { MatchCases, ofType, unionize, UnionOf } from 'common/unionize';
84 import { loadRunProcessPanel } from 'store/run-process-panel/run-process-panel-actions';
85 import {
86   collectionPanelActions,
87   loadCollectionPanel,
88 } from 'store/collection-panel/collection-panel-action';
89 import { CollectionResource } from 'models/collection';
90 import {
91   loadSearchResultsPanel,
92   searchResultsPanelActions,
93 } from 'store/search-results-panel/search-results-panel-actions';
94 import { searchResultsPanelColumns } from 'views/search-results-panel/search-results-panel-view';
95 import { loadVirtualMachinesPanel } from 'store/virtual-machines/virtual-machines-actions';
96 import { loadRepositoriesPanel } from 'store/repositories/repositories-actions';
97 import { loadKeepServicesPanel } from 'store/keep-services/keep-services-actions';
98 import { loadUsersPanel, userBindedActions } from 'store/users/users-actions';
99 import * as userProfilePanelActions from 'store/user-profile/user-profile-actions';
100 import {
101   linkPanelActions,
102   loadLinkPanel,
103 } from 'store/link-panel/link-panel-actions';
104 import { linkPanelColumns } from 'views/link-panel/link-panel-root';
105 import { userPanelColumns } from 'views/user-panel/user-panel';
106 import {
107   loadApiClientAuthorizationsPanel,
108   apiClientAuthorizationsActions,
109 } from 'store/api-client-authorizations/api-client-authorizations-actions';
110 import { apiClientAuthorizationPanelColumns } from 'views/api-client-authorization-panel/api-client-authorization-panel-root';
111 import * as groupPanelActions from 'store/groups-panel/groups-panel-actions';
112 import { groupsPanelColumns } from 'views/groups-panel/groups-panel';
113 import * as groupDetailsPanelActions from 'store/group-details-panel/group-details-panel-actions';
114 import {
115   groupDetailsMembersPanelColumns,
116   groupDetailsPermissionsPanelColumns,
117 } from 'views/group-details-panel/group-details-panel';
118 import { DataTableFetchMode } from 'components/data-table/data-table';
119 import {
120   loadPublicFavoritePanel,
121   publicFavoritePanelActions,
122 } from 'store/public-favorites-panel/public-favorites-action';
123 import { publicFavoritePanelColumns } from 'views/public-favorites-panel/public-favorites-panel';
124 import {
125   loadCollectionsContentAddressPanel,
126   collectionsContentAddressActions,
127 } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
128 import { collectionContentAddressPanelColumns } from 'views/collection-content-address-panel/collection-content-address-panel';
129 import { subprocessPanelActions } from 'store/subprocess-panel/subprocess-panel-actions';
130 import { subprocessPanelColumns } from 'views/subprocess-panel/subprocess-panel-root';
131 import {
132   loadAllProcessesPanel,
133   allProcessesPanelActions,
134 } from '../all-processes-panel/all-processes-panel-action';
135 import { allProcessesPanelColumns } from 'views/all-processes-panel/all-processes-panel';
136 import { AdminMenuIcon } from 'components/icon/icon';
137 import { userProfileGroupsColumns } from 'views/user-profile-panel/user-profile-panel-root';
138
139 export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
140
141 export const isWorkbenchLoading = (state: RootState) => {
142   const progress = getProgressIndicator(WORKBENCH_LOADING_SCREEN)(
143     state.progressIndicator
144   );
145   return progress ? progress.working : false;
146 };
147
148 export const handleFirstTimeLoad =
149   (action: any) =>
150   async (dispatch: Dispatch<any>, getState: () => RootState) => {
151     try {
152       await dispatch(action);
153     } finally {
154       if (isWorkbenchLoading(getState())) {
155         dispatch(
156           progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)
157         );
158       }
159     }
160   };
161
162 export const loadWorkbench =
163   () =>
164   async (
165     dispatch: Dispatch,
166     getState: () => RootState,
167     services: ServiceRepository
168   ) => {
169     dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
170     const { auth, router } = getState();
171     const { user } = auth;
172     if (user) {
173       dispatch(
174         projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns })
175       );
176       dispatch(
177         favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns })
178       );
179       dispatch(
180         allProcessesPanelActions.SET_COLUMNS({
181           columns: allProcessesPanelColumns,
182         })
183       );
184       dispatch(
185         publicFavoritePanelActions.SET_COLUMNS({
186           columns: publicFavoritePanelColumns,
187         })
188       );
189       dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
190       dispatch(
191         sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns })
192       );
193       dispatch(
194         workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns })
195       );
196       dispatch(
197         searchResultsPanelActions.SET_FETCH_MODE({
198           fetchMode: DataTableFetchMode.INFINITE,
199         })
200       );
201       dispatch(
202         searchResultsPanelActions.SET_COLUMNS({
203           columns: searchResultsPanelColumns,
204         })
205       );
206       dispatch(userBindedActions.SET_COLUMNS({ columns: userPanelColumns }));
207       dispatch(
208         groupPanelActions.GroupsPanelActions.SET_COLUMNS({
209           columns: groupsPanelColumns,
210         })
211       );
212       dispatch(
213         groupDetailsPanelActions.GroupMembersPanelActions.SET_COLUMNS({
214           columns: groupDetailsMembersPanelColumns,
215         })
216       );
217       dispatch(
218         groupDetailsPanelActions.GroupPermissionsPanelActions.SET_COLUMNS({
219           columns: groupDetailsPermissionsPanelColumns,
220         })
221       );
222       dispatch(
223         userProfilePanelActions.UserProfileGroupsActions.SET_COLUMNS({
224           columns: userProfileGroupsColumns,
225         })
226       );
227       dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns }));
228       dispatch(
229         apiClientAuthorizationsActions.SET_COLUMNS({
230           columns: apiClientAuthorizationPanelColumns,
231         })
232       );
233       dispatch(
234         collectionsContentAddressActions.SET_COLUMNS({
235           columns: collectionContentAddressPanelColumns,
236         })
237       );
238       dispatch(
239         subprocessPanelActions.SET_COLUMNS({ columns: subprocessPanelColumns })
240       );
241
242       if (services.linkAccountService.getAccountToLink()) {
243         dispatch(linkAccountPanelActions.HAS_SESSION_DATA());
244       }
245
246       dispatch<any>(initSidePanelTree());
247       if (router.location) {
248         const match = matchRootRoute(router.location.pathname);
249         if (match) {
250           dispatch<any>(navigateToRootProject);
251         }
252       }
253     } else {
254       dispatch(userIsNotAuthenticated);
255     }
256   };
257
258 export const loadFavorites = () =>
259   handleFirstTimeLoad((dispatch: Dispatch) => {
260     dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.FAVORITES));
261     dispatch<any>(loadFavoritePanel());
262     dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.FAVORITES));
263   });
264
265 export const loadCollectionContentAddress = handleFirstTimeLoad(
266   async (dispatch: Dispatch<any>) => {
267     await dispatch(loadCollectionsContentAddressPanel());
268   }
269 );
270
271 export const loadTrash = () =>
272   handleFirstTimeLoad((dispatch: Dispatch) => {
273     dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
274     dispatch<any>(loadTrashPanel());
275     dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.TRASH));
276   });
277
278 export const loadAllProcesses = () =>
279   handleFirstTimeLoad((dispatch: Dispatch) => {
280     dispatch<any>(
281       activateSidePanelTreeItem(SidePanelTreeCategory.ALL_PROCESSES)
282     );
283     dispatch<any>(loadAllProcessesPanel());
284     dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.ALL_PROCESSES));
285   });
286
287 export const loadProject = (uuid: string) =>
288   handleFirstTimeLoad(
289     async (
290       dispatch: Dispatch<any>,
291       getState: () => RootState,
292       services: ServiceRepository
293     ) => {
294       const userUuid = getUserUuid(getState());
295       dispatch(setIsProjectPanelTrashed(false));
296       if (!userUuid) {
297         return;
298       }
299       if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
300         // Load another users home projects
301         dispatch(finishLoadingProject(uuid));
302       } else if (userUuid !== uuid) {
303         await dispatch(finishLoadingProject(uuid));
304         const match = await loadGroupContentsResource({
305           uuid,
306           userUuid,
307           services,
308         });
309         match({
310           OWNED: async () => {
311             await dispatch(activateSidePanelTreeItem(uuid));
312             dispatch<any>(setSidePanelBreadcrumbs(uuid));
313           },
314           SHARED: async () => {
315             await dispatch(activateSidePanelTreeItem(uuid));
316             dispatch<any>(setSharedWithMeBreadcrumbs(uuid));
317           },
318           TRASHED: async () => {
319             await dispatch(
320               activateSidePanelTreeItem(SidePanelTreeCategory.TRASH)
321             );
322             dispatch<any>(setTrashBreadcrumbs(uuid));
323             dispatch(setIsProjectPanelTrashed(true));
324           },
325         });
326       } else {
327         await dispatch(finishLoadingProject(userUuid));
328         await dispatch(activateSidePanelTreeItem(userUuid));
329         dispatch<any>(setSidePanelBreadcrumbs(userUuid));
330       }
331     }
332   );
333
334 export const createProject =
335   (data: projectCreateActions.ProjectCreateFormDialogData) =>
336   async (dispatch: Dispatch) => {
337     const newProject = await dispatch<any>(
338       projectCreateActions.createProject(data)
339     );
340     if (newProject) {
341       dispatch(
342         snackbarActions.OPEN_SNACKBAR({
343           message: 'Project has been successfully created.',
344           hideDuration: 2000,
345           kind: SnackbarKind.SUCCESS,
346         })
347       );
348       await dispatch<any>(loadSidePanelTreeProjects(newProject.ownerUuid));
349       dispatch<any>(navigateTo(newProject.uuid));
350     }
351   };
352
353 export const moveProject =
354   (data: MoveToFormDialogData) =>
355   async (
356     dispatch: Dispatch,
357     getState: () => RootState,
358     services: ServiceRepository
359   ) => {
360     try {
361       const oldProject = getResource(data.uuid)(getState().resources);
362       const oldOwnerUuid = oldProject ? oldProject.ownerUuid : '';
363       const movedProject = await dispatch<any>(
364         projectMoveActions.moveProject(data)
365       );
366       if (movedProject) {
367         dispatch(
368           snackbarActions.OPEN_SNACKBAR({
369             message: 'Project has been moved',
370             hideDuration: 2000,
371             kind: SnackbarKind.SUCCESS,
372           })
373         );
374         if (oldProject) {
375           await dispatch<any>(loadSidePanelTreeProjects(oldProject.ownerUuid));
376         }
377         dispatch<any>(
378           reloadProjectMatchingUuid([
379             oldOwnerUuid,
380             movedProject.ownerUuid,
381             movedProject.uuid,
382           ])
383         );
384       }
385     } catch (e) {
386       dispatch(
387         snackbarActions.OPEN_SNACKBAR({
388           message: e.message,
389           hideDuration: 2000,
390           kind: SnackbarKind.ERROR,
391         })
392       );
393     }
394   };
395
396 export const updateProject =
397   (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
398   async (dispatch: Dispatch) => {
399     const updatedProject = await dispatch<any>(
400       projectUpdateActions.updateProject(data)
401     );
402     if (updatedProject) {
403       dispatch(
404         snackbarActions.OPEN_SNACKBAR({
405           message: 'Project has been successfully updated.',
406           hideDuration: 2000,
407           kind: SnackbarKind.SUCCESS,
408         })
409       );
410       await dispatch<any>(loadSidePanelTreeProjects(updatedProject.ownerUuid));
411       dispatch<any>(
412         reloadProjectMatchingUuid([
413           updatedProject.ownerUuid,
414           updatedProject.uuid,
415         ])
416       );
417     }
418   };
419
420 export const updateGroup =
421   (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
422   async (dispatch: Dispatch) => {
423     const updatedGroup = await dispatch<any>(
424       groupPanelActions.updateGroup(data)
425     );
426     if (updatedGroup) {
427       dispatch(
428         snackbarActions.OPEN_SNACKBAR({
429           message: 'Group has been successfully updated.',
430           hideDuration: 2000,
431           kind: SnackbarKind.SUCCESS,
432         })
433       );
434       await dispatch<any>(loadSidePanelTreeProjects(updatedGroup.ownerUuid));
435       dispatch<any>(
436         reloadProjectMatchingUuid([updatedGroup.ownerUuid, updatedGroup.uuid])
437       );
438     }
439   };
440
441 export const loadCollection = (uuid: string) =>
442   handleFirstTimeLoad(
443     async (
444       dispatch: Dispatch<any>,
445       getState: () => RootState,
446       services: ServiceRepository
447     ) => {
448       const userUuid = getUserUuid(getState());
449       if (userUuid) {
450         const match = await loadGroupContentsResource({
451           uuid,
452           userUuid,
453           services,
454         });
455         match({
456           OWNED: (collection) => {
457             dispatch(
458               collectionPanelActions.SET_COLLECTION(
459                 collection as CollectionResource
460               )
461             );
462             dispatch(updateResources([collection]));
463             dispatch(activateSidePanelTreeItem(collection.ownerUuid));
464             dispatch(setSidePanelBreadcrumbs(collection.ownerUuid));
465             dispatch(loadCollectionPanel(collection.uuid));
466           },
467           SHARED: (collection) => {
468             dispatch(
469               collectionPanelActions.SET_COLLECTION(
470                 collection as CollectionResource
471               )
472             );
473             dispatch(updateResources([collection]));
474             dispatch<any>(setSharedWithMeBreadcrumbs(collection.ownerUuid));
475             dispatch(activateSidePanelTreeItem(collection.ownerUuid));
476             dispatch(loadCollectionPanel(collection.uuid));
477           },
478           TRASHED: (collection) => {
479             dispatch(
480               collectionPanelActions.SET_COLLECTION(
481                 collection as CollectionResource
482               )
483             );
484             dispatch(updateResources([collection]));
485             dispatch(setTrashBreadcrumbs(''));
486             dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
487             dispatch(loadCollectionPanel(collection.uuid));
488           },
489         });
490       }
491     }
492   );
493
494 export const createCollection =
495   (data: collectionCreateActions.CollectionCreateFormDialogData) =>
496   async (dispatch: Dispatch) => {
497     const collection = await dispatch<any>(
498       collectionCreateActions.createCollection(data)
499     );
500     if (collection) {
501       dispatch(
502         snackbarActions.OPEN_SNACKBAR({
503           message: 'Collection has been successfully created.',
504           hideDuration: 2000,
505           kind: SnackbarKind.SUCCESS,
506         })
507       );
508       dispatch<any>(updateResources([collection]));
509       dispatch<any>(navigateTo(collection.uuid));
510     }
511   };
512
513 export const copyCollection =
514   (data: CopyFormDialogData) =>
515   async (
516     dispatch: Dispatch,
517     getState: () => RootState,
518     services: ServiceRepository
519   ) => {
520     try {
521       const copyToProject = getResource(data.ownerUuid)(getState().resources);
522       const collection = await dispatch<any>(
523         collectionCopyActions.copyCollection(data)
524       );
525       if (copyToProject && collection) {
526         dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
527         dispatch(
528           snackbarActions.OPEN_SNACKBAR({
529             message: 'Collection has been copied.',
530             hideDuration: 3000,
531             kind: SnackbarKind.SUCCESS,
532             link: collection.ownerUuid,
533           })
534         );
535       }
536     } catch (e) {
537       dispatch(
538         snackbarActions.OPEN_SNACKBAR({
539           message: e.message,
540           hideDuration: 2000,
541           kind: SnackbarKind.ERROR,
542         })
543       );
544     }
545   };
546
547 export const moveCollection =
548   (data: MoveToFormDialogData) =>
549   async (
550     dispatch: Dispatch,
551     getState: () => RootState,
552     services: ServiceRepository
553   ) => {
554     try {
555       const collection = await dispatch<any>(
556         collectionMoveActions.moveCollection(data)
557       );
558       dispatch<any>(updateResources([collection]));
559       dispatch<any>(reloadProjectMatchingUuid([collection.ownerUuid]));
560       dispatch(
561         snackbarActions.OPEN_SNACKBAR({
562           message: 'Collection has been moved.',
563           hideDuration: 2000,
564           kind: SnackbarKind.SUCCESS,
565         })
566       );
567     } catch (e) {
568       dispatch(
569         snackbarActions.OPEN_SNACKBAR({
570           message: e.message,
571           hideDuration: 2000,
572           kind: SnackbarKind.ERROR,
573         })
574       );
575     }
576   };
577
578 export const loadProcess = (uuid: string) =>
579   handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState) => {
580     dispatch<any>(loadProcessPanel(uuid));
581     const process = await dispatch<any>(processesActions.loadProcess(uuid));
582     if (process) {
583       await dispatch<any>(
584         activateSidePanelTreeItem(process.containerRequest.ownerUuid)
585       );
586       dispatch<any>(setProcessBreadcrumbs(uuid));
587       dispatch<any>(loadDetailsPanel(uuid));
588     }
589   });
590
591 export const updateProcess =
592   (data: processUpdateActions.ProcessUpdateFormDialogData) =>
593   async (dispatch: Dispatch) => {
594     try {
595       const process = await dispatch<any>(
596         processUpdateActions.updateProcess(data)
597       );
598       if (process) {
599         dispatch(
600           snackbarActions.OPEN_SNACKBAR({
601             message: 'Process has been successfully updated.',
602             hideDuration: 2000,
603             kind: SnackbarKind.SUCCESS,
604           })
605         );
606         dispatch<any>(updateResources([process]));
607         dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
608       }
609     } catch (e) {
610       dispatch(
611         snackbarActions.OPEN_SNACKBAR({
612           message: e.message,
613           hideDuration: 2000,
614           kind: SnackbarKind.ERROR,
615         })
616       );
617     }
618   };
619
620 export const moveProcess =
621   (data: MoveToFormDialogData) =>
622   async (
623     dispatch: Dispatch,
624     getState: () => RootState,
625     services: ServiceRepository
626   ) => {
627     try {
628       const process = await dispatch<any>(processMoveActions.moveProcess(data));
629       dispatch<any>(updateResources([process]));
630       dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
631       dispatch(
632         snackbarActions.OPEN_SNACKBAR({
633           message: 'Process has been moved.',
634           hideDuration: 2000,
635           kind: SnackbarKind.SUCCESS,
636         })
637       );
638     } catch (e) {
639       dispatch(
640         snackbarActions.OPEN_SNACKBAR({
641           message: e.message,
642           hideDuration: 2000,
643           kind: SnackbarKind.ERROR,
644         })
645       );
646     }
647   };
648
649 export const copyProcess =
650   (data: CopyFormDialogData) =>
651   async (
652     dispatch: Dispatch,
653     getState: () => RootState,
654     services: ServiceRepository
655   ) => {
656     try {
657       const process = await dispatch<any>(processCopyActions.copyProcess(data));
658       dispatch<any>(updateResources([process]));
659       dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
660       dispatch(
661         snackbarActions.OPEN_SNACKBAR({
662           message: 'Process has been copied.',
663           hideDuration: 2000,
664           kind: SnackbarKind.SUCCESS,
665         })
666       );
667     } catch (e) {
668       dispatch(
669         snackbarActions.OPEN_SNACKBAR({
670           message: e.message,
671           hideDuration: 2000,
672           kind: SnackbarKind.ERROR,
673         })
674       );
675     }
676   };
677
678 export const resourceIsNotLoaded = (uuid: string) =>
679   snackbarActions.OPEN_SNACKBAR({
680     message: `Resource identified by ${uuid} is not loaded.`,
681     kind: SnackbarKind.ERROR,
682   });
683
684 export const userIsNotAuthenticated = snackbarActions.OPEN_SNACKBAR({
685   message: 'User is not authenticated',
686   kind: SnackbarKind.ERROR,
687 });
688
689 export const couldNotLoadUser = snackbarActions.OPEN_SNACKBAR({
690   message: 'Could not load user',
691   kind: SnackbarKind.ERROR,
692 });
693
694 export const reloadProjectMatchingUuid =
695   (matchingUuids: string[]) =>
696   async (
697     dispatch: Dispatch,
698     getState: () => RootState,
699     services: ServiceRepository
700   ) => {
701     const currentProjectPanelUuid = getProjectPanelCurrentUuid(getState());
702     if (
703       currentProjectPanelUuid &&
704       matchingUuids.some((uuid) => uuid === currentProjectPanelUuid)
705     ) {
706       dispatch<any>(loadProject(currentProjectPanelUuid));
707     }
708   };
709
710 export const loadSharedWithMe = handleFirstTimeLoad(
711   async (dispatch: Dispatch) => {
712     dispatch<any>(loadSharedWithMePanel());
713     await dispatch<any>(
714       activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)
715     );
716     await dispatch<any>(
717       setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)
718     );
719   }
720 );
721
722 export const loadRunProcess = handleFirstTimeLoad(
723   async (dispatch: Dispatch) => {
724     await dispatch<any>(loadRunProcessPanel());
725   }
726 );
727
728 export const loadPublicFavorites = () =>
729   handleFirstTimeLoad((dispatch: Dispatch) => {
730     dispatch<any>(
731       activateSidePanelTreeItem(SidePanelTreeCategory.PUBLIC_FAVORITES)
732     );
733     dispatch<any>(loadPublicFavoritePanel());
734     dispatch<any>(
735       setSidePanelBreadcrumbs(SidePanelTreeCategory.PUBLIC_FAVORITES)
736     );
737   });
738
739 export const loadSearchResults = handleFirstTimeLoad(
740   async (dispatch: Dispatch<any>) => {
741     await dispatch(loadSearchResultsPanel());
742   }
743 );
744
745 export const loadLinks = handleFirstTimeLoad(
746   async (dispatch: Dispatch<any>) => {
747     await dispatch(loadLinkPanel());
748   }
749 );
750
751 export const loadVirtualMachines = handleFirstTimeLoad(
752   async (dispatch: Dispatch<any>) => {
753     await dispatch(loadVirtualMachinesPanel());
754     dispatch(setBreadcrumbs([{ label: 'Virtual Machines' }]));
755   }
756 );
757
758 export const loadVirtualMachinesAdmin = handleFirstTimeLoad(
759   async (dispatch: Dispatch<any>) => {
760     await dispatch(loadVirtualMachinesPanel());
761     dispatch(
762       setBreadcrumbs([{ label: 'Virtual Machines Admin', icon: AdminMenuIcon }])
763     );
764   }
765 );
766
767 export const loadRepositories = handleFirstTimeLoad(
768   async (dispatch: Dispatch<any>) => {
769     await dispatch(loadRepositoriesPanel());
770     dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
771   }
772 );
773
774 export const loadSshKeys = handleFirstTimeLoad(
775   async (dispatch: Dispatch<any>) => {
776     await dispatch(loadSshKeysPanel());
777   }
778 );
779
780 export const loadSiteManager = handleFirstTimeLoad(
781   async (dispatch: Dispatch<any>) => {
782     await dispatch(loadSiteManagerPanel());
783   }
784 );
785
786 export const loadUserProfile = (userUuid?: string) =>
787   handleFirstTimeLoad((dispatch: Dispatch<any>) => {
788     if (userUuid) {
789       dispatch(setUserProfileBreadcrumbs(userUuid));
790       dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
791     } else {
792       dispatch(setMyAccountBreadcrumbs());
793       dispatch(userProfilePanelActions.loadUserProfilePanel());
794     }
795   });
796
797 export const loadLinkAccount = handleFirstTimeLoad(
798   (dispatch: Dispatch<any>) => {
799     dispatch(loadLinkAccountPanel());
800   }
801 );
802
803 export const loadKeepServices = handleFirstTimeLoad(
804   async (dispatch: Dispatch<any>) => {
805     await dispatch(loadKeepServicesPanel());
806   }
807 );
808
809 export const loadUsers = handleFirstTimeLoad(
810   async (dispatch: Dispatch<any>) => {
811     await dispatch(loadUsersPanel());
812     dispatch(setUsersBreadcrumbs());
813   }
814 );
815
816 export const loadApiClientAuthorizations = handleFirstTimeLoad(
817   async (dispatch: Dispatch<any>) => {
818     await dispatch(loadApiClientAuthorizationsPanel());
819   }
820 );
821
822 export const loadGroupsPanel = handleFirstTimeLoad(
823   (dispatch: Dispatch<any>) => {
824     dispatch(setGroupsBreadcrumbs());
825     dispatch(groupPanelActions.loadGroupsPanel());
826   }
827 );
828
829 export const loadGroupDetailsPanel = (groupUuid: string) =>
830   handleFirstTimeLoad((dispatch: Dispatch<any>) => {
831     dispatch(setGroupDetailsBreadcrumbs(groupUuid));
832     dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
833   });
834
835 const finishLoadingProject =
836   (project: GroupContentsResource | string) =>
837   async (dispatch: Dispatch<any>) => {
838     const uuid = typeof project === 'string' ? project : project.uuid;
839     dispatch(openProjectPanel(uuid));
840     dispatch(loadDetailsPanel(uuid));
841     if (typeof project !== 'string') {
842       dispatch(updateResources([project]));
843     }
844   };
845
846 const loadGroupContentsResource = async (params: {
847   uuid: string;
848   userUuid: string;
849   services: ServiceRepository;
850 }) => {
851   const filters = new FilterBuilder()
852     .addEqual('uuid', params.uuid)
853     .getFilters();
854   const { items } = await params.services.groupsService.contents(
855     params.userUuid,
856     {
857       filters,
858       recursive: true,
859       includeTrash: true,
860     }
861   );
862   const resource = items.shift();
863   let handler: GroupContentsHandler;
864   if (resource) {
865     handler =
866       (resource.kind === ResourceKind.COLLECTION ||
867         resource.kind === ResourceKind.PROJECT) &&
868       resource.isTrashed
869         ? groupContentsHandlers.TRASHED(resource)
870         : groupContentsHandlers.OWNED(resource);
871   } else {
872     const kind = extractUuidKind(params.uuid);
873     let resource: GroupContentsResource;
874     if (kind === ResourceKind.COLLECTION) {
875       resource = await params.services.collectionService.get(params.uuid);
876     } else if (kind === ResourceKind.PROJECT) {
877       resource = await params.services.projectService.get(params.uuid);
878     } else {
879       resource = await params.services.containerRequestService.get(params.uuid);
880     }
881     handler = groupContentsHandlers.SHARED(resource);
882   }
883   return (
884     cases: MatchCases<
885       typeof groupContentsHandlersRecord,
886       GroupContentsHandler,
887       void
888     >
889   ) => groupContentsHandlers.match(handler, cases);
890 };
891
892 const groupContentsHandlersRecord = {
893   TRASHED: ofType<GroupContentsResource>(),
894   SHARED: ofType<GroupContentsResource>(),
895   OWNED: ofType<GroupContentsResource>(),
896 };
897
898 const groupContentsHandlers = unionize(groupContentsHandlersRecord);
899
900 type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;