19482: Fix context menu, breadcrumbs
[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     setWorkflowBreadcrumbs,
36     setSharedWithMeBreadcrumbs,
37     setSidePanelBreadcrumbs,
38     setTrashBreadcrumbs,
39     setUsersBreadcrumbs,
40     setMyAccountBreadcrumbs,
41     setUserProfileBreadcrumbs,
42 } from 'store/breadcrumbs/breadcrumbs-actions';
43 import {
44     navigateTo,
45     navigateToRootProject,
46 } from 'store/navigation/navigation-action';
47 import { MoveToFormDialogData } from 'store/move-to-dialog/move-to-dialog';
48 import { ServiceRepository } from 'services/services';
49 import { getResource } from 'store/resources/resources';
50 import * as projectCreateActions from 'store/projects/project-create-actions';
51 import * as projectMoveActions from 'store/projects/project-move-actions';
52 import * as projectUpdateActions from 'store/projects/project-update-actions';
53 import * as collectionCreateActions from 'store/collections/collection-create-actions';
54 import * as collectionCopyActions from 'store/collections/collection-copy-actions';
55 import * as collectionMoveActions from 'store/collections/collection-move-actions';
56 import * as processesActions from 'store/processes/processes-actions';
57 import * as processMoveActions from 'store/processes/process-move-actions';
58 import * as processUpdateActions from 'store/processes/process-update-actions';
59 import * as processCopyActions from 'store/processes/process-copy-actions';
60 import { trashPanelColumns } from 'views/trash-panel/trash-panel';
61 import {
62     loadTrashPanel,
63     trashPanelActions,
64 } from 'store/trash-panel/trash-panel-action';
65 import { loadProcessPanel } from 'store/process-panel/process-panel-actions';
66 import {
67     loadSharedWithMePanel,
68     sharedWithMePanelActions,
69 } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
70 import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog';
71 import { workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions';
72 import { loadSshKeysPanel } from 'store/auth/auth-action-ssh';
73 import {
74     loadLinkAccountPanel,
75     linkAccountPanelActions,
76 } from 'store/link-account-panel/link-account-panel-actions';
77 import { loadSiteManagerPanel } from 'store/auth/auth-action-session';
78 import { workflowPanelColumns } from 'views/workflow-panel/workflow-panel-view';
79 import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
80 import { getProgressIndicator } from 'store/progress-indicator/progress-indicator-reducer';
81 import { extractUuidKind, ResourceKind } from 'models/resource';
82 import { FilterBuilder } from 'services/api/filter-builder';
83 import { GroupContentsResource } from 'services/groups-service/groups-service';
84 import { MatchCases, ofType, unionize, UnionOf } from 'common/unionize';
85 import { loadRunProcessPanel } from 'store/run-process-panel/run-process-panel-actions';
86 import {
87     collectionPanelActions,
88     loadCollectionPanel,
89 } from 'store/collection-panel/collection-panel-action';
90 import { CollectionResource } from 'models/collection';
91 import {
92     loadSearchResultsPanel,
93     searchResultsPanelActions,
94 } from 'store/search-results-panel/search-results-panel-actions';
95 import { searchResultsPanelColumns } from 'views/search-results-panel/search-results-panel-view';
96 import { loadVirtualMachinesPanel } from 'store/virtual-machines/virtual-machines-actions';
97 import { loadRepositoriesPanel } from 'store/repositories/repositories-actions';
98 import { loadKeepServicesPanel } from 'store/keep-services/keep-services-actions';
99 import { loadUsersPanel, userBindedActions } from 'store/users/users-actions';
100 import * as userProfilePanelActions from 'store/user-profile/user-profile-actions';
101 import {
102     linkPanelActions,
103     loadLinkPanel,
104 } from 'store/link-panel/link-panel-actions';
105 import { linkPanelColumns } from 'views/link-panel/link-panel-root';
106 import { userPanelColumns } from 'views/user-panel/user-panel';
107 import {
108     loadApiClientAuthorizationsPanel,
109     apiClientAuthorizationsActions,
110 } from 'store/api-client-authorizations/api-client-authorizations-actions';
111 import { apiClientAuthorizationPanelColumns } from 'views/api-client-authorization-panel/api-client-authorization-panel-root';
112 import * as groupPanelActions from 'store/groups-panel/groups-panel-actions';
113 import { groupsPanelColumns } from 'views/groups-panel/groups-panel';
114 import * as groupDetailsPanelActions from 'store/group-details-panel/group-details-panel-actions';
115 import {
116     groupDetailsMembersPanelColumns,
117     groupDetailsPermissionsPanelColumns,
118 } from 'views/group-details-panel/group-details-panel';
119 import { DataTableFetchMode } from 'components/data-table/data-table';
120 import {
121     loadPublicFavoritePanel,
122     publicFavoritePanelActions,
123 } from 'store/public-favorites-panel/public-favorites-action';
124 import { publicFavoritePanelColumns } from 'views/public-favorites-panel/public-favorites-panel';
125 import {
126     loadCollectionsContentAddressPanel,
127     collectionsContentAddressActions,
128 } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
129 import { collectionContentAddressPanelColumns } from 'views/collection-content-address-panel/collection-content-address-panel';
130 import { subprocessPanelActions } from 'store/subprocess-panel/subprocess-panel-actions';
131 import { subprocessPanelColumns } from 'views/subprocess-panel/subprocess-panel-root';
132 import {
133     loadAllProcessesPanel,
134     allProcessesPanelActions,
135 } from '../all-processes-panel/all-processes-panel-action';
136 import { allProcessesPanelColumns } from 'views/all-processes-panel/all-processes-panel';
137 import { AdminMenuIcon } from 'components/icon/icon';
138 import { userProfileGroupsColumns } from 'views/user-profile-panel/user-profile-panel-root';
139
140 export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
141
142 export const isWorkbenchLoading = (state: RootState) => {
143     const progress = getProgressIndicator(WORKBENCH_LOADING_SCREEN)(
144         state.progressIndicator
145     );
146     return progress ? progress.working : false;
147 };
148
149 export const handleFirstTimeLoad =
150     (action: any) =>
151         async (dispatch: Dispatch<any>, getState: () => RootState) => {
152             try {
153                 await dispatch(action);
154             } finally {
155                 if (isWorkbenchLoading(getState())) {
156                     dispatch(
157                         progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)
158                     );
159                 }
160             }
161         };
162
163 export const loadWorkbench =
164     () =>
165         async (
166             dispatch: Dispatch,
167             getState: () => RootState,
168             services: ServiceRepository
169         ) => {
170             dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
171             const { auth, router } = getState();
172             const { user } = auth;
173             if (user) {
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         if (process) {
584             await dispatch<any>(
585                 activateSidePanelTreeItem(process.containerRequest.ownerUuid)
586             );
587             dispatch<any>(setProcessBreadcrumbs(uuid));
588             dispatch<any>(loadDetailsPanel(uuid));
589         }
590     });
591
592 export const loadRegisteredWorkflow = (uuid: string) =>
593     handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
594         const workflow = await services.workflowService.get(uuid);
595         if (workflow) {
596             dispatch<any>(updateResources([workflow]));
597             await dispatch<any>(
598                 activateSidePanelTreeItem(workflow.ownerUuid)
599             );
600             dispatch<any>(setWorkflowBreadcrumbs(uuid));
601         }
602     });
603
604 export const updateProcess =
605     (data: processUpdateActions.ProcessUpdateFormDialogData) =>
606         async (dispatch: Dispatch) => {
607             try {
608                 const process = await dispatch<any>(
609                     processUpdateActions.updateProcess(data)
610                 );
611                 if (process) {
612                     dispatch(
613                         snackbarActions.OPEN_SNACKBAR({
614                             message: 'Process has been successfully updated.',
615                             hideDuration: 2000,
616                             kind: SnackbarKind.SUCCESS,
617                         })
618                     );
619                     dispatch<any>(updateResources([process]));
620                     dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
621                 }
622             } catch (e) {
623                 dispatch(
624                     snackbarActions.OPEN_SNACKBAR({
625                         message: e.message,
626                         hideDuration: 2000,
627                         kind: SnackbarKind.ERROR,
628                     })
629                 );
630             }
631         };
632
633 export const moveProcess =
634     (data: MoveToFormDialogData) =>
635         async (
636             dispatch: Dispatch,
637             getState: () => RootState,
638             services: ServiceRepository
639         ) => {
640             try {
641                 const process = await dispatch<any>(processMoveActions.moveProcess(data));
642                 dispatch<any>(updateResources([process]));
643                 dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
644                 dispatch(
645                     snackbarActions.OPEN_SNACKBAR({
646                         message: 'Process has been moved.',
647                         hideDuration: 2000,
648                         kind: SnackbarKind.SUCCESS,
649                     })
650                 );
651             } catch (e) {
652                 dispatch(
653                     snackbarActions.OPEN_SNACKBAR({
654                         message: e.message,
655                         hideDuration: 2000,
656                         kind: SnackbarKind.ERROR,
657                     })
658                 );
659             }
660         };
661
662 export const copyProcess =
663     (data: CopyFormDialogData) =>
664         async (
665             dispatch: Dispatch,
666             getState: () => RootState,
667             services: ServiceRepository
668         ) => {
669             try {
670                 const process = await dispatch<any>(processCopyActions.copyProcess(data));
671                 dispatch<any>(updateResources([process]));
672                 dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
673                 dispatch(
674                     snackbarActions.OPEN_SNACKBAR({
675                         message: 'Process has been copied.',
676                         hideDuration: 2000,
677                         kind: SnackbarKind.SUCCESS,
678                     })
679                 );
680                 dispatch<any>(navigateTo(process.uuid));
681             } catch (e) {
682                 dispatch(
683                     snackbarActions.OPEN_SNACKBAR({
684                         message: e.message,
685                         hideDuration: 2000,
686                         kind: SnackbarKind.ERROR,
687                     })
688                 );
689             }
690         };
691
692 export const resourceIsNotLoaded = (uuid: string) =>
693     snackbarActions.OPEN_SNACKBAR({
694         message: `Resource identified by ${uuid} is not loaded.`,
695         kind: SnackbarKind.ERROR,
696     });
697
698 export const userIsNotAuthenticated = snackbarActions.OPEN_SNACKBAR({
699     message: 'User is not authenticated',
700     kind: SnackbarKind.ERROR,
701 });
702
703 export const couldNotLoadUser = snackbarActions.OPEN_SNACKBAR({
704     message: 'Could not load user',
705     kind: SnackbarKind.ERROR,
706 });
707
708 export const reloadProjectMatchingUuid =
709     (matchingUuids: string[]) =>
710         async (
711             dispatch: Dispatch,
712             getState: () => RootState,
713             services: ServiceRepository
714         ) => {
715             const currentProjectPanelUuid = getProjectPanelCurrentUuid(getState());
716             if (
717                 currentProjectPanelUuid &&
718                 matchingUuids.some((uuid) => uuid === currentProjectPanelUuid)
719             ) {
720                 dispatch<any>(loadProject(currentProjectPanelUuid));
721             }
722         };
723
724 export const loadSharedWithMe = handleFirstTimeLoad(
725     async (dispatch: Dispatch) => {
726         dispatch<any>(loadSharedWithMePanel());
727         await dispatch<any>(
728             activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)
729         );
730         await dispatch<any>(
731             setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)
732         );
733     }
734 );
735
736 export const loadRunProcess = handleFirstTimeLoad(
737     async (dispatch: Dispatch) => {
738         await dispatch<any>(loadRunProcessPanel());
739     }
740 );
741
742 export const loadPublicFavorites = () =>
743     handleFirstTimeLoad((dispatch: Dispatch) => {
744         dispatch<any>(
745             activateSidePanelTreeItem(SidePanelTreeCategory.PUBLIC_FAVORITES)
746         );
747         dispatch<any>(loadPublicFavoritePanel());
748         dispatch<any>(
749             setSidePanelBreadcrumbs(SidePanelTreeCategory.PUBLIC_FAVORITES)
750         );
751     });
752
753 export const loadSearchResults = handleFirstTimeLoad(
754     async (dispatch: Dispatch<any>) => {
755         await dispatch(loadSearchResultsPanel());
756     }
757 );
758
759 export const loadLinks = handleFirstTimeLoad(
760     async (dispatch: Dispatch<any>) => {
761         await dispatch(loadLinkPanel());
762     }
763 );
764
765 export const loadVirtualMachines = handleFirstTimeLoad(
766     async (dispatch: Dispatch<any>) => {
767         await dispatch(loadVirtualMachinesPanel());
768         dispatch(setBreadcrumbs([{ label: 'Virtual Machines' }]));
769     }
770 );
771
772 export const loadVirtualMachinesAdmin = handleFirstTimeLoad(
773     async (dispatch: Dispatch<any>) => {
774         await dispatch(loadVirtualMachinesPanel());
775         dispatch(
776             setBreadcrumbs([{ label: 'Virtual Machines Admin', icon: AdminMenuIcon }])
777         );
778     }
779 );
780
781 export const loadRepositories = handleFirstTimeLoad(
782     async (dispatch: Dispatch<any>) => {
783         await dispatch(loadRepositoriesPanel());
784         dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
785     }
786 );
787
788 export const loadSshKeys = handleFirstTimeLoad(
789     async (dispatch: Dispatch<any>) => {
790         await dispatch(loadSshKeysPanel());
791     }
792 );
793
794 export const loadSiteManager = handleFirstTimeLoad(
795     async (dispatch: Dispatch<any>) => {
796         await dispatch(loadSiteManagerPanel());
797     }
798 );
799
800 export const loadUserProfile = (userUuid?: string) =>
801     handleFirstTimeLoad((dispatch: Dispatch<any>) => {
802         if (userUuid) {
803             dispatch(setUserProfileBreadcrumbs(userUuid));
804             dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
805         } else {
806             dispatch(setMyAccountBreadcrumbs());
807             dispatch(userProfilePanelActions.loadUserProfilePanel());
808         }
809     });
810
811 export const loadLinkAccount = handleFirstTimeLoad(
812     (dispatch: Dispatch<any>) => {
813         dispatch(loadLinkAccountPanel());
814     }
815 );
816
817 export const loadKeepServices = handleFirstTimeLoad(
818     async (dispatch: Dispatch<any>) => {
819         await dispatch(loadKeepServicesPanel());
820     }
821 );
822
823 export const loadUsers = handleFirstTimeLoad(
824     async (dispatch: Dispatch<any>) => {
825         await dispatch(loadUsersPanel());
826         dispatch(setUsersBreadcrumbs());
827     }
828 );
829
830 export const loadApiClientAuthorizations = handleFirstTimeLoad(
831     async (dispatch: Dispatch<any>) => {
832         await dispatch(loadApiClientAuthorizationsPanel());
833     }
834 );
835
836 export const loadGroupsPanel = handleFirstTimeLoad(
837     (dispatch: Dispatch<any>) => {
838         dispatch(setGroupsBreadcrumbs());
839         dispatch(groupPanelActions.loadGroupsPanel());
840     }
841 );
842
843 export const loadGroupDetailsPanel = (groupUuid: string) =>
844     handleFirstTimeLoad((dispatch: Dispatch<any>) => {
845         dispatch(setGroupDetailsBreadcrumbs(groupUuid));
846         dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
847     });
848
849 const finishLoadingProject =
850     (project: GroupContentsResource | string) =>
851         async (dispatch: Dispatch<any>) => {
852             const uuid = typeof project === 'string' ? project : project.uuid;
853             dispatch(openProjectPanel(uuid));
854             dispatch(loadDetailsPanel(uuid));
855             if (typeof project !== 'string') {
856                 dispatch(updateResources([project]));
857             }
858         };
859
860 const loadGroupContentsResource = async (params: {
861     uuid: string;
862     userUuid: string;
863     services: ServiceRepository;
864 }) => {
865     const filters = new FilterBuilder()
866         .addEqual('uuid', params.uuid)
867         .getFilters();
868     const { items } = await params.services.groupsService.contents(
869         params.userUuid,
870         {
871             filters,
872             recursive: true,
873             includeTrash: true,
874         }
875     );
876     const resource = items.shift();
877     let handler: GroupContentsHandler;
878     if (resource) {
879         handler =
880             (resource.kind === ResourceKind.COLLECTION ||
881                 resource.kind === ResourceKind.PROJECT) &&
882                 resource.isTrashed
883                 ? groupContentsHandlers.TRASHED(resource)
884                 : groupContentsHandlers.OWNED(resource);
885     } else {
886         const kind = extractUuidKind(params.uuid);
887         let resource: GroupContentsResource;
888         if (kind === ResourceKind.COLLECTION) {
889             resource = await params.services.collectionService.get(params.uuid);
890         } else if (kind === ResourceKind.PROJECT) {
891             resource = await params.services.projectService.get(params.uuid);
892         } else {
893             resource = await params.services.containerRequestService.get(params.uuid);
894         }
895         handler = groupContentsHandlers.SHARED(resource);
896     }
897     return (
898         cases: MatchCases<
899             typeof groupContentsHandlersRecord,
900             GroupContentsHandler,
901             void
902         >
903     ) => groupContentsHandlers.match(handler, cases);
904 };
905
906 const groupContentsHandlersRecord = {
907     TRASHED: ofType<GroupContentsResource>(),
908     SHARED: ofType<GroupContentsResource>(),
909     OWNED: ofType<GroupContentsResource>(),
910 };
911
912 const groupContentsHandlers = unionize(groupContentsHandlersRecord);
913
914 type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;