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