19690: uuid column goodish
[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       //   console.log('PREJECTPANELCOLUMNS', projectPanelColumns);
174       dispatch(
175         projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns })
176       );
177       dispatch(
178         favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns })
179       );
180       dispatch(
181         allProcessesPanelActions.SET_COLUMNS({
182           columns: allProcessesPanelColumns,
183         })
184       );
185       dispatch(
186         publicFavoritePanelActions.SET_COLUMNS({
187           columns: publicFavoritePanelColumns,
188         })
189       );
190       dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
191       dispatch(
192         sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns })
193       );
194       dispatch(
195         workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns })
196       );
197       dispatch(
198         searchResultsPanelActions.SET_FETCH_MODE({
199           fetchMode: DataTableFetchMode.INFINITE,
200         })
201       );
202       dispatch(
203         searchResultsPanelActions.SET_COLUMNS({
204           columns: searchResultsPanelColumns,
205         })
206       );
207       dispatch(userBindedActions.SET_COLUMNS({ columns: userPanelColumns }));
208       dispatch(
209         groupPanelActions.GroupsPanelActions.SET_COLUMNS({
210           columns: groupsPanelColumns,
211         })
212       );
213       dispatch(
214         groupDetailsPanelActions.GroupMembersPanelActions.SET_COLUMNS({
215           columns: groupDetailsMembersPanelColumns,
216         })
217       );
218       dispatch(
219         groupDetailsPanelActions.GroupPermissionsPanelActions.SET_COLUMNS({
220           columns: groupDetailsPermissionsPanelColumns,
221         })
222       );
223       dispatch(
224         userProfilePanelActions.UserProfileGroupsActions.SET_COLUMNS({
225           columns: userProfileGroupsColumns,
226         })
227       );
228       dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns }));
229       dispatch(
230         apiClientAuthorizationsActions.SET_COLUMNS({
231           columns: apiClientAuthorizationPanelColumns,
232         })
233       );
234       dispatch(
235         collectionsContentAddressActions.SET_COLUMNS({
236           columns: collectionContentAddressPanelColumns,
237         })
238       );
239       dispatch(
240         subprocessPanelActions.SET_COLUMNS({ columns: subprocessPanelColumns })
241       );
242
243       if (services.linkAccountService.getAccountToLink()) {
244         dispatch(linkAccountPanelActions.HAS_SESSION_DATA());
245       }
246
247       dispatch<any>(initSidePanelTree());
248       if (router.location) {
249         const match = matchRootRoute(router.location.pathname);
250         if (match) {
251           dispatch<any>(navigateToRootProject);
252         }
253       }
254     } else {
255       dispatch(userIsNotAuthenticated);
256     }
257   };
258
259 export const loadFavorites = () =>
260   handleFirstTimeLoad((dispatch: Dispatch) => {
261     dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.FAVORITES));
262     dispatch<any>(loadFavoritePanel());
263     dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.FAVORITES));
264   });
265
266 export const loadCollectionContentAddress = handleFirstTimeLoad(
267   async (dispatch: Dispatch<any>) => {
268     await dispatch(loadCollectionsContentAddressPanel());
269   }
270 );
271
272 export const loadTrash = () =>
273   handleFirstTimeLoad((dispatch: Dispatch) => {
274     dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
275     dispatch<any>(loadTrashPanel());
276     dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.TRASH));
277   });
278
279 export const loadAllProcesses = () =>
280   handleFirstTimeLoad((dispatch: Dispatch) => {
281     dispatch<any>(
282       activateSidePanelTreeItem(SidePanelTreeCategory.ALL_PROCESSES)
283     );
284     dispatch<any>(loadAllProcessesPanel());
285     dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.ALL_PROCESSES));
286   });
287
288 export const loadProject = (uuid: string) =>
289   handleFirstTimeLoad(
290     async (
291       dispatch: Dispatch<any>,
292       getState: () => RootState,
293       services: ServiceRepository
294     ) => {
295       const userUuid = getUserUuid(getState());
296       dispatch(setIsProjectPanelTrashed(false));
297       if (!userUuid) {
298         return;
299       }
300       if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
301         // Load another users home projects
302         dispatch(finishLoadingProject(uuid));
303       } else if (userUuid !== uuid) {
304         await dispatch(finishLoadingProject(uuid));
305         const match = await loadGroupContentsResource({
306           uuid,
307           userUuid,
308           services,
309         });
310         match({
311           OWNED: async () => {
312             await dispatch(activateSidePanelTreeItem(uuid));
313             dispatch<any>(setSidePanelBreadcrumbs(uuid));
314           },
315           SHARED: async () => {
316             await dispatch(activateSidePanelTreeItem(uuid));
317             dispatch<any>(setSharedWithMeBreadcrumbs(uuid));
318           },
319           TRASHED: async () => {
320             await dispatch(
321               activateSidePanelTreeItem(SidePanelTreeCategory.TRASH)
322             );
323             dispatch<any>(setTrashBreadcrumbs(uuid));
324             dispatch(setIsProjectPanelTrashed(true));
325           },
326         });
327       } else {
328         await dispatch(finishLoadingProject(userUuid));
329         await dispatch(activateSidePanelTreeItem(userUuid));
330         dispatch<any>(setSidePanelBreadcrumbs(userUuid));
331       }
332     }
333   );
334
335 export const createProject =
336   (data: projectCreateActions.ProjectCreateFormDialogData) =>
337   async (dispatch: Dispatch) => {
338     const newProject = await dispatch<any>(
339       projectCreateActions.createProject(data)
340     );
341     if (newProject) {
342       dispatch(
343         snackbarActions.OPEN_SNACKBAR({
344           message: 'Project has been successfully created.',
345           hideDuration: 2000,
346           kind: SnackbarKind.SUCCESS,
347         })
348       );
349       await dispatch<any>(loadSidePanelTreeProjects(newProject.ownerUuid));
350       dispatch<any>(navigateTo(newProject.uuid));
351     }
352   };
353
354 export const moveProject =
355   (data: MoveToFormDialogData) =>
356   async (
357     dispatch: Dispatch,
358     getState: () => RootState,
359     services: ServiceRepository
360   ) => {
361     try {
362       const oldProject = getResource(data.uuid)(getState().resources);
363       const oldOwnerUuid = oldProject ? oldProject.ownerUuid : '';
364       const movedProject = await dispatch<any>(
365         projectMoveActions.moveProject(data)
366       );
367       if (movedProject) {
368         dispatch(
369           snackbarActions.OPEN_SNACKBAR({
370             message: 'Project has been moved',
371             hideDuration: 2000,
372             kind: SnackbarKind.SUCCESS,
373           })
374         );
375         if (oldProject) {
376           await dispatch<any>(loadSidePanelTreeProjects(oldProject.ownerUuid));
377         }
378         dispatch<any>(
379           reloadProjectMatchingUuid([
380             oldOwnerUuid,
381             movedProject.ownerUuid,
382             movedProject.uuid,
383           ])
384         );
385       }
386     } catch (e) {
387       dispatch(
388         snackbarActions.OPEN_SNACKBAR({
389           message: e.message,
390           hideDuration: 2000,
391           kind: SnackbarKind.ERROR,
392         })
393       );
394     }
395   };
396
397 export const updateProject =
398   (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
399   async (dispatch: Dispatch) => {
400     const updatedProject = await dispatch<any>(
401       projectUpdateActions.updateProject(data)
402     );
403     if (updatedProject) {
404       dispatch(
405         snackbarActions.OPEN_SNACKBAR({
406           message: 'Project has been successfully updated.',
407           hideDuration: 2000,
408           kind: SnackbarKind.SUCCESS,
409         })
410       );
411       await dispatch<any>(loadSidePanelTreeProjects(updatedProject.ownerUuid));
412       dispatch<any>(
413         reloadProjectMatchingUuid([
414           updatedProject.ownerUuid,
415           updatedProject.uuid,
416         ])
417       );
418     }
419   };
420
421 export const updateGroup =
422   (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
423   async (dispatch: Dispatch) => {
424     const updatedGroup = await dispatch<any>(
425       groupPanelActions.updateGroup(data)
426     );
427     if (updatedGroup) {
428       dispatch(
429         snackbarActions.OPEN_SNACKBAR({
430           message: 'Group has been successfully updated.',
431           hideDuration: 2000,
432           kind: SnackbarKind.SUCCESS,
433         })
434       );
435       await dispatch<any>(loadSidePanelTreeProjects(updatedGroup.ownerUuid));
436       dispatch<any>(
437         reloadProjectMatchingUuid([updatedGroup.ownerUuid, updatedGroup.uuid])
438       );
439     }
440   };
441
442 export const loadCollection = (uuid: string) =>
443   handleFirstTimeLoad(
444     async (
445       dispatch: Dispatch<any>,
446       getState: () => RootState,
447       services: ServiceRepository
448     ) => {
449       const userUuid = getUserUuid(getState());
450       if (userUuid) {
451         const match = await loadGroupContentsResource({
452           uuid,
453           userUuid,
454           services,
455         });
456         match({
457           OWNED: (collection) => {
458             dispatch(
459               collectionPanelActions.SET_COLLECTION(
460                 collection as CollectionResource
461               )
462             );
463             dispatch(updateResources([collection]));
464             dispatch(activateSidePanelTreeItem(collection.ownerUuid));
465             dispatch(setSidePanelBreadcrumbs(collection.ownerUuid));
466             dispatch(loadCollectionPanel(collection.uuid));
467           },
468           SHARED: (collection) => {
469             dispatch(
470               collectionPanelActions.SET_COLLECTION(
471                 collection as CollectionResource
472               )
473             );
474             dispatch(updateResources([collection]));
475             dispatch<any>(setSharedWithMeBreadcrumbs(collection.ownerUuid));
476             dispatch(activateSidePanelTreeItem(collection.ownerUuid));
477             dispatch(loadCollectionPanel(collection.uuid));
478           },
479           TRASHED: (collection) => {
480             dispatch(
481               collectionPanelActions.SET_COLLECTION(
482                 collection as CollectionResource
483               )
484             );
485             dispatch(updateResources([collection]));
486             dispatch(setTrashBreadcrumbs(''));
487             dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
488             dispatch(loadCollectionPanel(collection.uuid));
489           },
490         });
491       }
492     }
493   );
494
495 export const createCollection =
496   (data: collectionCreateActions.CollectionCreateFormDialogData) =>
497   async (dispatch: Dispatch) => {
498     const collection = await dispatch<any>(
499       collectionCreateActions.createCollection(data)
500     );
501     if (collection) {
502       dispatch(
503         snackbarActions.OPEN_SNACKBAR({
504           message: 'Collection has been successfully created.',
505           hideDuration: 2000,
506           kind: SnackbarKind.SUCCESS,
507         })
508       );
509       dispatch<any>(updateResources([collection]));
510       dispatch<any>(navigateTo(collection.uuid));
511     }
512   };
513
514 export const copyCollection =
515   (data: CopyFormDialogData) =>
516   async (
517     dispatch: Dispatch,
518     getState: () => RootState,
519     services: ServiceRepository
520   ) => {
521     try {
522       const copyToProject = getResource(data.ownerUuid)(getState().resources);
523       const collection = await dispatch<any>(
524         collectionCopyActions.copyCollection(data)
525       );
526       if (copyToProject && collection) {
527         dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
528         dispatch(
529           snackbarActions.OPEN_SNACKBAR({
530             message: 'Collection has been copied.',
531             hideDuration: 3000,
532             kind: SnackbarKind.SUCCESS,
533             link: collection.ownerUuid,
534           })
535         );
536       }
537     } catch (e) {
538       dispatch(
539         snackbarActions.OPEN_SNACKBAR({
540           message: e.message,
541           hideDuration: 2000,
542           kind: SnackbarKind.ERROR,
543         })
544       );
545     }
546   };
547
548 export const moveCollection =
549   (data: MoveToFormDialogData) =>
550   async (
551     dispatch: Dispatch,
552     getState: () => RootState,
553     services: ServiceRepository
554   ) => {
555     try {
556       const collection = await dispatch<any>(
557         collectionMoveActions.moveCollection(data)
558       );
559       dispatch<any>(updateResources([collection]));
560       dispatch<any>(reloadProjectMatchingUuid([collection.ownerUuid]));
561       dispatch(
562         snackbarActions.OPEN_SNACKBAR({
563           message: 'Collection has been moved.',
564           hideDuration: 2000,
565           kind: SnackbarKind.SUCCESS,
566         })
567       );
568     } catch (e) {
569       dispatch(
570         snackbarActions.OPEN_SNACKBAR({
571           message: e.message,
572           hideDuration: 2000,
573           kind: SnackbarKind.ERROR,
574         })
575       );
576     }
577   };
578
579 export const loadProcess = (uuid: string) =>
580   handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState) => {
581     dispatch<any>(loadProcessPanel(uuid));
582     const process = await dispatch<any>(processesActions.loadProcess(uuid));
583     await dispatch<any>(
584       activateSidePanelTreeItem(process.containerRequest.ownerUuid)
585     );
586     dispatch<any>(setProcessBreadcrumbs(uuid));
587     dispatch<any>(loadDetailsPanel(uuid));
588   });
589
590 export const updateProcess =
591   (data: processUpdateActions.ProcessUpdateFormDialogData) =>
592   async (dispatch: Dispatch) => {
593     try {
594       const process = await dispatch<any>(
595         processUpdateActions.updateProcess(data)
596       );
597       if (process) {
598         dispatch(
599           snackbarActions.OPEN_SNACKBAR({
600             message: 'Process has been successfully updated.',
601             hideDuration: 2000,
602             kind: SnackbarKind.SUCCESS,
603           })
604         );
605         dispatch<any>(updateResources([process]));
606         dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
607       }
608     } catch (e) {
609       dispatch(
610         snackbarActions.OPEN_SNACKBAR({
611           message: e.message,
612           hideDuration: 2000,
613           kind: SnackbarKind.ERROR,
614         })
615       );
616     }
617   };
618
619 export const moveProcess =
620   (data: MoveToFormDialogData) =>
621   async (
622     dispatch: Dispatch,
623     getState: () => RootState,
624     services: ServiceRepository
625   ) => {
626     try {
627       const process = await dispatch<any>(processMoveActions.moveProcess(data));
628       dispatch<any>(updateResources([process]));
629       dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
630       dispatch(
631         snackbarActions.OPEN_SNACKBAR({
632           message: 'Process has been moved.',
633           hideDuration: 2000,
634           kind: SnackbarKind.SUCCESS,
635         })
636       );
637     } catch (e) {
638       dispatch(
639         snackbarActions.OPEN_SNACKBAR({
640           message: e.message,
641           hideDuration: 2000,
642           kind: SnackbarKind.ERROR,
643         })
644       );
645     }
646   };
647
648 export const copyProcess =
649   (data: CopyFormDialogData) =>
650   async (
651     dispatch: Dispatch,
652     getState: () => RootState,
653     services: ServiceRepository
654   ) => {
655     try {
656       const process = await dispatch<any>(processCopyActions.copyProcess(data));
657       dispatch<any>(updateResources([process]));
658       dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
659       dispatch(
660         snackbarActions.OPEN_SNACKBAR({
661           message: 'Process has been copied.',
662           hideDuration: 2000,
663           kind: SnackbarKind.SUCCESS,
664         })
665       );
666     } catch (e) {
667       dispatch(
668         snackbarActions.OPEN_SNACKBAR({
669           message: e.message,
670           hideDuration: 2000,
671           kind: SnackbarKind.ERROR,
672         })
673       );
674     }
675   };
676
677 export const resourceIsNotLoaded = (uuid: string) =>
678   snackbarActions.OPEN_SNACKBAR({
679     message: `Resource identified by ${uuid} is not loaded.`,
680     kind: SnackbarKind.ERROR,
681   });
682
683 export const userIsNotAuthenticated = snackbarActions.OPEN_SNACKBAR({
684   message: 'User is not authenticated',
685   kind: SnackbarKind.ERROR,
686 });
687
688 export const couldNotLoadUser = snackbarActions.OPEN_SNACKBAR({
689   message: 'Could not load user',
690   kind: SnackbarKind.ERROR,
691 });
692
693 export const reloadProjectMatchingUuid =
694   (matchingUuids: string[]) =>
695   async (
696     dispatch: Dispatch,
697     getState: () => RootState,
698     services: ServiceRepository
699   ) => {
700     const currentProjectPanelUuid = getProjectPanelCurrentUuid(getState());
701     if (
702       currentProjectPanelUuid &&
703       matchingUuids.some((uuid) => uuid === currentProjectPanelUuid)
704     ) {
705       dispatch<any>(loadProject(currentProjectPanelUuid));
706     }
707   };
708
709 export const loadSharedWithMe = handleFirstTimeLoad(
710   async (dispatch: Dispatch) => {
711     dispatch<any>(loadSharedWithMePanel());
712     await dispatch<any>(
713       activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)
714     );
715     await dispatch<any>(
716       setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)
717     );
718   }
719 );
720
721 export const loadRunProcess = handleFirstTimeLoad(
722   async (dispatch: Dispatch) => {
723     await dispatch<any>(loadRunProcessPanel());
724   }
725 );
726
727 export const loadPublicFavorites = () =>
728   handleFirstTimeLoad((dispatch: Dispatch) => {
729     dispatch<any>(
730       activateSidePanelTreeItem(SidePanelTreeCategory.PUBLIC_FAVORITES)
731     );
732     dispatch<any>(loadPublicFavoritePanel());
733     dispatch<any>(
734       setSidePanelBreadcrumbs(SidePanelTreeCategory.PUBLIC_FAVORITES)
735     );
736   });
737
738 export const loadSearchResults = handleFirstTimeLoad(
739   async (dispatch: Dispatch<any>) => {
740     await dispatch(loadSearchResultsPanel());
741   }
742 );
743
744 export const loadLinks = handleFirstTimeLoad(
745   async (dispatch: Dispatch<any>) => {
746     await dispatch(loadLinkPanel());
747   }
748 );
749
750 export const loadVirtualMachines = handleFirstTimeLoad(
751   async (dispatch: Dispatch<any>) => {
752     await dispatch(loadVirtualMachinesPanel());
753     dispatch(setBreadcrumbs([{ label: 'Virtual Machines' }]));
754   }
755 );
756
757 export const loadVirtualMachinesAdmin = handleFirstTimeLoad(
758   async (dispatch: Dispatch<any>) => {
759     await dispatch(loadVirtualMachinesPanel());
760     dispatch(
761       setBreadcrumbs([{ label: 'Virtual Machines Admin', icon: AdminMenuIcon }])
762     );
763   }
764 );
765
766 export const loadRepositories = handleFirstTimeLoad(
767   async (dispatch: Dispatch<any>) => {
768     await dispatch(loadRepositoriesPanel());
769     dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
770   }
771 );
772
773 export const loadSshKeys = handleFirstTimeLoad(
774   async (dispatch: Dispatch<any>) => {
775     await dispatch(loadSshKeysPanel());
776   }
777 );
778
779 export const loadSiteManager = handleFirstTimeLoad(
780   async (dispatch: Dispatch<any>) => {
781     await dispatch(loadSiteManagerPanel());
782   }
783 );
784
785 export const loadUserProfile = (userUuid?: string) =>
786   handleFirstTimeLoad((dispatch: Dispatch<any>) => {
787     if (userUuid) {
788       dispatch(setUserProfileBreadcrumbs(userUuid));
789       dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
790     } else {
791       dispatch(setMyAccountBreadcrumbs());
792       dispatch(userProfilePanelActions.loadUserProfilePanel());
793     }
794   });
795
796 export const loadLinkAccount = handleFirstTimeLoad(
797   (dispatch: Dispatch<any>) => {
798     dispatch(loadLinkAccountPanel());
799   }
800 );
801
802 export const loadKeepServices = handleFirstTimeLoad(
803   async (dispatch: Dispatch<any>) => {
804     await dispatch(loadKeepServicesPanel());
805   }
806 );
807
808 export const loadUsers = handleFirstTimeLoad(
809   async (dispatch: Dispatch<any>) => {
810     await dispatch(loadUsersPanel());
811     dispatch(setUsersBreadcrumbs());
812   }
813 );
814
815 export const loadApiClientAuthorizations = handleFirstTimeLoad(
816   async (dispatch: Dispatch<any>) => {
817     await dispatch(loadApiClientAuthorizationsPanel());
818   }
819 );
820
821 export const loadGroupsPanel = handleFirstTimeLoad(
822   (dispatch: Dispatch<any>) => {
823     dispatch(setGroupsBreadcrumbs());
824     dispatch(groupPanelActions.loadGroupsPanel());
825   }
826 );
827
828 export const loadGroupDetailsPanel = (groupUuid: string) =>
829   handleFirstTimeLoad((dispatch: Dispatch<any>) => {
830     dispatch(setGroupDetailsBreadcrumbs(groupUuid));
831     dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
832   });
833
834 const finishLoadingProject =
835   (project: GroupContentsResource | string) =>
836   async (dispatch: Dispatch<any>) => {
837     const uuid = typeof project === 'string' ? project : project.uuid;
838     dispatch(openProjectPanel(uuid));
839     dispatch(loadDetailsPanel(uuid));
840     if (typeof project !== 'string') {
841       dispatch(updateResources([project]));
842     }
843   };
844
845 const loadGroupContentsResource = async (params: {
846   uuid: string;
847   userUuid: string;
848   services: ServiceRepository;
849 }) => {
850   const filters = new FilterBuilder()
851     .addEqual('uuid', params.uuid)
852     .getFilters();
853   const { items } = await params.services.groupsService.contents(
854     params.userUuid,
855     {
856       filters,
857       recursive: true,
858       includeTrash: true,
859     }
860   );
861   const resource = items.shift();
862   let handler: GroupContentsHandler;
863   if (resource) {
864     handler =
865       (resource.kind === ResourceKind.COLLECTION ||
866         resource.kind === ResourceKind.PROJECT) &&
867       resource.isTrashed
868         ? groupContentsHandlers.TRASHED(resource)
869         : groupContentsHandlers.OWNED(resource);
870   } else {
871     const kind = extractUuidKind(params.uuid);
872     let resource: GroupContentsResource;
873     if (kind === ResourceKind.COLLECTION) {
874       resource = await params.services.collectionService.get(params.uuid);
875     } else if (kind === ResourceKind.PROJECT) {
876       resource = await params.services.projectService.get(params.uuid);
877     } else {
878       resource = await params.services.containerRequestService.get(params.uuid);
879     }
880     handler = groupContentsHandlers.SHARED(resource);
881   }
882   return (
883     cases: MatchCases<
884       typeof groupContentsHandlersRecord,
885       GroupContentsHandler,
886       void
887     >
888   ) => groupContentsHandlers.match(handler, cases);
889 };
890
891 const groupContentsHandlersRecord = {
892   TRASHED: ofType<GroupContentsResource>(),
893   SHARED: ofType<GroupContentsResource>(),
894   OWNED: ofType<GroupContentsResource>(),
895 };
896
897 const groupContentsHandlers = unionize(groupContentsHandlersRecord);
898
899 type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;