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