19690: created and deleteAt columns up
[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     await dispatch<any>(
583       activateSidePanelTreeItem(process.containerRequest.ownerUuid)
584     );
585     dispatch<any>(setProcessBreadcrumbs(uuid));
586     dispatch<any>(loadDetailsPanel(uuid));
587   });
588
589 export const updateProcess =
590   (data: processUpdateActions.ProcessUpdateFormDialogData) =>
591   async (dispatch: Dispatch) => {
592     try {
593       const process = await dispatch<any>(
594         processUpdateActions.updateProcess(data)
595       );
596       if (process) {
597         dispatch(
598           snackbarActions.OPEN_SNACKBAR({
599             message: 'Process has been successfully updated.',
600             hideDuration: 2000,
601             kind: SnackbarKind.SUCCESS,
602           })
603         );
604         dispatch<any>(updateResources([process]));
605         dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
606       }
607     } catch (e) {
608       dispatch(
609         snackbarActions.OPEN_SNACKBAR({
610           message: e.message,
611           hideDuration: 2000,
612           kind: SnackbarKind.ERROR,
613         })
614       );
615     }
616   };
617
618 export const moveProcess =
619   (data: MoveToFormDialogData) =>
620   async (
621     dispatch: Dispatch,
622     getState: () => RootState,
623     services: ServiceRepository
624   ) => {
625     try {
626       const process = await dispatch<any>(processMoveActions.moveProcess(data));
627       dispatch<any>(updateResources([process]));
628       dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
629       dispatch(
630         snackbarActions.OPEN_SNACKBAR({
631           message: 'Process has been moved.',
632           hideDuration: 2000,
633           kind: SnackbarKind.SUCCESS,
634         })
635       );
636     } catch (e) {
637       dispatch(
638         snackbarActions.OPEN_SNACKBAR({
639           message: e.message,
640           hideDuration: 2000,
641           kind: SnackbarKind.ERROR,
642         })
643       );
644     }
645   };
646
647 export const copyProcess =
648   (data: CopyFormDialogData) =>
649   async (
650     dispatch: Dispatch,
651     getState: () => RootState,
652     services: ServiceRepository
653   ) => {
654     try {
655       const process = await dispatch<any>(processCopyActions.copyProcess(data));
656       dispatch<any>(updateResources([process]));
657       dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
658       dispatch(
659         snackbarActions.OPEN_SNACKBAR({
660           message: 'Process has been copied.',
661           hideDuration: 2000,
662           kind: SnackbarKind.SUCCESS,
663         })
664       );
665     } catch (e) {
666       dispatch(
667         snackbarActions.OPEN_SNACKBAR({
668           message: e.message,
669           hideDuration: 2000,
670           kind: SnackbarKind.ERROR,
671         })
672       );
673     }
674   };
675
676 export const resourceIsNotLoaded = (uuid: string) =>
677   snackbarActions.OPEN_SNACKBAR({
678     message: `Resource identified by ${uuid} is not loaded.`,
679     kind: SnackbarKind.ERROR,
680   });
681
682 export const userIsNotAuthenticated = snackbarActions.OPEN_SNACKBAR({
683   message: 'User is not authenticated',
684   kind: SnackbarKind.ERROR,
685 });
686
687 export const couldNotLoadUser = snackbarActions.OPEN_SNACKBAR({
688   message: 'Could not load user',
689   kind: SnackbarKind.ERROR,
690 });
691
692 export const reloadProjectMatchingUuid =
693   (matchingUuids: string[]) =>
694   async (
695     dispatch: Dispatch,
696     getState: () => RootState,
697     services: ServiceRepository
698   ) => {
699     const currentProjectPanelUuid = getProjectPanelCurrentUuid(getState());
700     if (
701       currentProjectPanelUuid &&
702       matchingUuids.some((uuid) => uuid === currentProjectPanelUuid)
703     ) {
704       dispatch<any>(loadProject(currentProjectPanelUuid));
705     }
706   };
707
708 export const loadSharedWithMe = handleFirstTimeLoad(
709   async (dispatch: Dispatch) => {
710     dispatch<any>(loadSharedWithMePanel());
711     await dispatch<any>(
712       activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)
713     );
714     await dispatch<any>(
715       setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)
716     );
717   }
718 );
719
720 export const loadRunProcess = handleFirstTimeLoad(
721   async (dispatch: Dispatch) => {
722     await dispatch<any>(loadRunProcessPanel());
723   }
724 );
725
726 export const loadPublicFavorites = () =>
727   handleFirstTimeLoad((dispatch: Dispatch) => {
728     dispatch<any>(
729       activateSidePanelTreeItem(SidePanelTreeCategory.PUBLIC_FAVORITES)
730     );
731     dispatch<any>(loadPublicFavoritePanel());
732     dispatch<any>(
733       setSidePanelBreadcrumbs(SidePanelTreeCategory.PUBLIC_FAVORITES)
734     );
735   });
736
737 export const loadSearchResults = handleFirstTimeLoad(
738   async (dispatch: Dispatch<any>) => {
739     await dispatch(loadSearchResultsPanel());
740   }
741 );
742
743 export const loadLinks = handleFirstTimeLoad(
744   async (dispatch: Dispatch<any>) => {
745     await dispatch(loadLinkPanel());
746   }
747 );
748
749 export const loadVirtualMachines = handleFirstTimeLoad(
750   async (dispatch: Dispatch<any>) => {
751     await dispatch(loadVirtualMachinesPanel());
752     dispatch(setBreadcrumbs([{ label: 'Virtual Machines' }]));
753   }
754 );
755
756 export const loadVirtualMachinesAdmin = handleFirstTimeLoad(
757   async (dispatch: Dispatch<any>) => {
758     await dispatch(loadVirtualMachinesPanel());
759     dispatch(
760       setBreadcrumbs([{ label: 'Virtual Machines Admin', icon: AdminMenuIcon }])
761     );
762   }
763 );
764
765 export const loadRepositories = handleFirstTimeLoad(
766   async (dispatch: Dispatch<any>) => {
767     await dispatch(loadRepositoriesPanel());
768     dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
769   }
770 );
771
772 export const loadSshKeys = handleFirstTimeLoad(
773   async (dispatch: Dispatch<any>) => {
774     await dispatch(loadSshKeysPanel());
775   }
776 );
777
778 export const loadSiteManager = handleFirstTimeLoad(
779   async (dispatch: Dispatch<any>) => {
780     await dispatch(loadSiteManagerPanel());
781   }
782 );
783
784 export const loadUserProfile = (userUuid?: string) =>
785   handleFirstTimeLoad((dispatch: Dispatch<any>) => {
786     if (userUuid) {
787       dispatch(setUserProfileBreadcrumbs(userUuid));
788       dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
789     } else {
790       dispatch(setMyAccountBreadcrumbs());
791       dispatch(userProfilePanelActions.loadUserProfilePanel());
792     }
793   });
794
795 export const loadLinkAccount = handleFirstTimeLoad(
796   (dispatch: Dispatch<any>) => {
797     dispatch(loadLinkAccountPanel());
798   }
799 );
800
801 export const loadKeepServices = handleFirstTimeLoad(
802   async (dispatch: Dispatch<any>) => {
803     await dispatch(loadKeepServicesPanel());
804   }
805 );
806
807 export const loadUsers = handleFirstTimeLoad(
808   async (dispatch: Dispatch<any>) => {
809     await dispatch(loadUsersPanel());
810     dispatch(setUsersBreadcrumbs());
811   }
812 );
813
814 export const loadApiClientAuthorizations = handleFirstTimeLoad(
815   async (dispatch: Dispatch<any>) => {
816     await dispatch(loadApiClientAuthorizationsPanel());
817   }
818 );
819
820 export const loadGroupsPanel = handleFirstTimeLoad(
821   (dispatch: Dispatch<any>) => {
822     dispatch(setGroupsBreadcrumbs());
823     dispatch(groupPanelActions.loadGroupsPanel());
824   }
825 );
826
827 export const loadGroupDetailsPanel = (groupUuid: string) =>
828   handleFirstTimeLoad((dispatch: Dispatch<any>) => {
829     dispatch(setGroupDetailsBreadcrumbs(groupUuid));
830     dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
831   });
832
833 const finishLoadingProject =
834   (project: GroupContentsResource | string) =>
835   async (dispatch: Dispatch<any>) => {
836     const uuid = typeof project === 'string' ? project : project.uuid;
837     dispatch(openProjectPanel(uuid));
838     dispatch(loadDetailsPanel(uuid));
839     if (typeof project !== 'string') {
840       dispatch(updateResources([project]));
841     }
842   };
843
844 const loadGroupContentsResource = async (params: {
845   uuid: string;
846   userUuid: string;
847   services: ServiceRepository;
848 }) => {
849   const filters = new FilterBuilder()
850     .addEqual('uuid', params.uuid)
851     .getFilters();
852   const { items } = await params.services.groupsService.contents(
853     params.userUuid,
854     {
855       filters,
856       recursive: true,
857       includeTrash: true,
858     }
859   );
860   const resource = items.shift();
861   let handler: GroupContentsHandler;
862   if (resource) {
863     handler =
864       (resource.kind === ResourceKind.COLLECTION ||
865         resource.kind === ResourceKind.PROJECT) &&
866       resource.isTrashed
867         ? groupContentsHandlers.TRASHED(resource)
868         : groupContentsHandlers.OWNED(resource);
869   } else {
870     const kind = extractUuidKind(params.uuid);
871     let resource: GroupContentsResource;
872     if (kind === ResourceKind.COLLECTION) {
873       resource = await params.services.collectionService.get(params.uuid);
874     } else if (kind === ResourceKind.PROJECT) {
875       resource = await params.services.projectService.get(params.uuid);
876     } else {
877       resource = await params.services.containerRequestService.get(params.uuid);
878     }
879     handler = groupContentsHandlers.SHARED(resource);
880   }
881   return (
882     cases: MatchCases<
883       typeof groupContentsHandlersRecord,
884       GroupContentsHandler,
885       void
886     >
887   ) => groupContentsHandlers.match(handler, cases);
888 };
889
890 const groupContentsHandlersRecord = {
891   TRASHED: ofType<GroupContentsResource>(),
892   SHARED: ofType<GroupContentsResource>(),
893   OWNED: ofType<GroupContentsResource>(),
894 };
895
896 const groupContentsHandlers = unionize(groupContentsHandlersRecord);
897
898 type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;