524337796efe37a6cfc472c9174eb3a7bd6012bd
[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 { WorkflowResource } from 'models/workflow';
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                 let collection: CollectionResource | undefined;
457                 let breadcrumbfunc: ((uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => Promise<void>) | undefined;
458                 let sidepanel: string | undefined;
459                 match({
460                     OWNED: (thecollection) => {
461                         collection = thecollection as CollectionResource;
462                         sidepanel = collection.ownerUuid;
463                         breadcrumbfunc = setSidePanelBreadcrumbs;
464                     },
465                     SHARED: (thecollection) => {
466                         collection = thecollection as CollectionResource;
467                         sidepanel = collection.ownerUuid;
468                         breadcrumbfunc = setSharedWithMeBreadcrumbs;
469                     },
470                     TRASHED: (thecollection) => {
471                         collection = thecollection as CollectionResource;
472                         sidepanel = SidePanelTreeCategory.TRASH;
473                         breadcrumbfunc = () => setTrashBreadcrumbs('');
474                     },
475                 });
476                 if (collection && breadcrumbfunc && sidepanel) {
477                     dispatch(updateResources([collection]));
478                     await dispatch<any>(finishLoadingProject(collection.ownerUuid));
479                     dispatch(collectionPanelActions.SET_COLLECTION(collection));
480                     await dispatch(activateSidePanelTreeItem(sidepanel));
481                     dispatch(breadcrumbfunc(collection.ownerUuid));
482                     dispatch(loadCollectionPanel(collection.uuid));
483                 }
484             }
485         }
486     );
487
488 export const createCollection =
489     (data: collectionCreateActions.CollectionCreateFormDialogData) =>
490         async (dispatch: Dispatch) => {
491             const collection = await dispatch<any>(
492                 collectionCreateActions.createCollection(data)
493             );
494             if (collection) {
495                 dispatch(
496                     snackbarActions.OPEN_SNACKBAR({
497                         message: 'Collection has been successfully created.',
498                         hideDuration: 2000,
499                         kind: SnackbarKind.SUCCESS,
500                     })
501                 );
502                 dispatch<any>(updateResources([collection]));
503                 dispatch<any>(navigateTo(collection.uuid));
504             }
505         };
506
507 export const copyCollection =
508     (data: CopyFormDialogData) =>
509         async (
510             dispatch: Dispatch,
511             getState: () => RootState,
512             services: ServiceRepository
513         ) => {
514             try {
515                 const copyToProject = getResource(data.ownerUuid)(getState().resources);
516                 const collection = await dispatch<any>(
517                     collectionCopyActions.copyCollection(data)
518                 );
519                 if (copyToProject && collection) {
520                     dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
521                     dispatch(
522                         snackbarActions.OPEN_SNACKBAR({
523                             message: 'Collection has been copied.',
524                             hideDuration: 3000,
525                             kind: SnackbarKind.SUCCESS,
526                             link: collection.ownerUuid,
527                         })
528                     );
529                 }
530             } catch (e) {
531                 dispatch(
532                     snackbarActions.OPEN_SNACKBAR({
533                         message: e.message,
534                         hideDuration: 2000,
535                         kind: SnackbarKind.ERROR,
536                     })
537                 );
538             }
539         };
540
541 export const moveCollection =
542     (data: MoveToFormDialogData) =>
543         async (
544             dispatch: Dispatch,
545             getState: () => RootState,
546             services: ServiceRepository
547         ) => {
548             try {
549                 const collection = await dispatch<any>(
550                     collectionMoveActions.moveCollection(data)
551                 );
552                 dispatch<any>(updateResources([collection]));
553                 dispatch<any>(reloadProjectMatchingUuid([collection.ownerUuid]));
554                 dispatch(
555                     snackbarActions.OPEN_SNACKBAR({
556                         message: 'Collection has been moved.',
557                         hideDuration: 2000,
558                         kind: SnackbarKind.SUCCESS,
559                     })
560                 );
561             } catch (e) {
562                 dispatch(
563                     snackbarActions.OPEN_SNACKBAR({
564                         message: e.message,
565                         hideDuration: 2000,
566                         kind: SnackbarKind.ERROR,
567                     })
568                 );
569             }
570         };
571
572 export const loadProcess = (uuid: string) =>
573     handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState) => {
574         dispatch<any>(loadProcessPanel(uuid));
575         const process = await dispatch<any>(processesActions.loadProcess(uuid));
576         if (process) {
577             await dispatch<any>(finishLoadingProject(process.containerRequest.ownerUuid));
578             await dispatch<any>(
579                 activateSidePanelTreeItem(process.containerRequest.ownerUuid)
580             );
581             dispatch<any>(setProcessBreadcrumbs(uuid));
582             dispatch<any>(loadDetailsPanel(uuid));
583         }
584     });
585
586 export const loadRegisteredWorkflow = (uuid: string) =>
587     handleFirstTimeLoad(async (dispatch: Dispatch,
588         getState: () => RootState,
589         services: ServiceRepository) => {
590
591         const userUuid = getUserUuid(getState());
592         if (userUuid) {
593             const match = await loadGroupContentsResource({
594                 uuid,
595                 userUuid,
596                 services,
597             });
598             let workflow: WorkflowResource | undefined;
599             let breadcrumbfunc: ((uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => Promise<void>) | undefined;
600             match({
601                 OWNED: async (theworkflow) => {
602                     workflow = theworkflow as WorkflowResource;
603                     breadcrumbfunc = setSidePanelBreadcrumbs;
604                 },
605                 SHARED: async (theworkflow) => {
606                     workflow = theworkflow as WorkflowResource;
607                     breadcrumbfunc = setSharedWithMeBreadcrumbs;
608                 },
609                 TRASHED: () => { }
610             });
611             if (workflow && breadcrumbfunc) {
612                 dispatch(updateResources([workflow]));
613                 await dispatch<any>(finishLoadingProject(workflow.ownerUuid));
614                 await dispatch<any>(activateSidePanelTreeItem(workflow.ownerUuid));
615                 dispatch<any>(breadcrumbfunc(workflow.ownerUuid));
616             }
617         }
618     });
619
620 export const updateProcess =
621     (data: processUpdateActions.ProcessUpdateFormDialogData) =>
622         async (dispatch: Dispatch) => {
623             try {
624                 const process = await dispatch<any>(
625                     processUpdateActions.updateProcess(data)
626                 );
627                 if (process) {
628                     dispatch(
629                         snackbarActions.OPEN_SNACKBAR({
630                             message: 'Process has been successfully updated.',
631                             hideDuration: 2000,
632                             kind: SnackbarKind.SUCCESS,
633                         })
634                     );
635                     dispatch<any>(updateResources([process]));
636                     dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
637                 }
638             } catch (e) {
639                 dispatch(
640                     snackbarActions.OPEN_SNACKBAR({
641                         message: e.message,
642                         hideDuration: 2000,
643                         kind: SnackbarKind.ERROR,
644                     })
645                 );
646             }
647         };
648
649 export const moveProcess =
650     (data: MoveToFormDialogData) =>
651         async (
652             dispatch: Dispatch,
653             getState: () => RootState,
654             services: ServiceRepository
655         ) => {
656             try {
657                 const process = await dispatch<any>(processMoveActions.moveProcess(data));
658                 dispatch<any>(updateResources([process]));
659                 dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
660                 dispatch(
661                     snackbarActions.OPEN_SNACKBAR({
662                         message: 'Process has been moved.',
663                         hideDuration: 2000,
664                         kind: SnackbarKind.SUCCESS,
665                     })
666                 );
667             } catch (e) {
668                 dispatch(
669                     snackbarActions.OPEN_SNACKBAR({
670                         message: e.message,
671                         hideDuration: 2000,
672                         kind: SnackbarKind.ERROR,
673                     })
674                 );
675             }
676         };
677
678 export const copyProcess =
679     (data: CopyFormDialogData) =>
680         async (
681             dispatch: Dispatch,
682             getState: () => RootState,
683             services: ServiceRepository
684         ) => {
685             try {
686                 const process = await dispatch<any>(processCopyActions.copyProcess(data));
687                 dispatch<any>(updateResources([process]));
688                 dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
689                 dispatch(
690                     snackbarActions.OPEN_SNACKBAR({
691                         message: 'Process has been copied.',
692                         hideDuration: 2000,
693                         kind: SnackbarKind.SUCCESS,
694                     })
695                 );
696                 dispatch<any>(navigateTo(process.uuid));
697             } catch (e) {
698                 dispatch(
699                     snackbarActions.OPEN_SNACKBAR({
700                         message: e.message,
701                         hideDuration: 2000,
702                         kind: SnackbarKind.ERROR,
703                     })
704                 );
705             }
706         };
707
708 export const resourceIsNotLoaded = (uuid: string) =>
709     snackbarActions.OPEN_SNACKBAR({
710         message: `Resource identified by ${uuid} is not loaded.`,
711         kind: SnackbarKind.ERROR,
712     });
713
714 export const userIsNotAuthenticated = snackbarActions.OPEN_SNACKBAR({
715     message: 'User is not authenticated',
716     kind: SnackbarKind.ERROR,
717 });
718
719 export const couldNotLoadUser = snackbarActions.OPEN_SNACKBAR({
720     message: 'Could not load user',
721     kind: SnackbarKind.ERROR,
722 });
723
724 export const reloadProjectMatchingUuid =
725     (matchingUuids: string[]) =>
726         async (
727             dispatch: Dispatch,
728             getState: () => RootState,
729             services: ServiceRepository
730         ) => {
731             const currentProjectPanelUuid = getProjectPanelCurrentUuid(getState());
732             if (
733                 currentProjectPanelUuid &&
734                 matchingUuids.some((uuid) => uuid === currentProjectPanelUuid)
735             ) {
736                 dispatch<any>(loadProject(currentProjectPanelUuid));
737             }
738         };
739
740 export const loadSharedWithMe = handleFirstTimeLoad(
741     async (dispatch: Dispatch) => {
742         dispatch<any>(loadSharedWithMePanel());
743         await dispatch<any>(
744             activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)
745         );
746         await dispatch<any>(
747             setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)
748         );
749     }
750 );
751
752 export const loadRunProcess = handleFirstTimeLoad(
753     async (dispatch: Dispatch) => {
754         await dispatch<any>(loadRunProcessPanel());
755     }
756 );
757
758 export const loadPublicFavorites = () =>
759     handleFirstTimeLoad((dispatch: Dispatch) => {
760         dispatch<any>(
761             activateSidePanelTreeItem(SidePanelTreeCategory.PUBLIC_FAVORITES)
762         );
763         dispatch<any>(loadPublicFavoritePanel());
764         dispatch<any>(
765             setSidePanelBreadcrumbs(SidePanelTreeCategory.PUBLIC_FAVORITES)
766         );
767     });
768
769 export const loadSearchResults = handleFirstTimeLoad(
770     async (dispatch: Dispatch<any>) => {
771         await dispatch(loadSearchResultsPanel());
772     }
773 );
774
775 export const loadLinks = handleFirstTimeLoad(
776     async (dispatch: Dispatch<any>) => {
777         await dispatch(loadLinkPanel());
778     }
779 );
780
781 export const loadVirtualMachines = handleFirstTimeLoad(
782     async (dispatch: Dispatch<any>) => {
783         await dispatch(loadVirtualMachinesPanel());
784         dispatch(setBreadcrumbs([{ label: 'Virtual Machines' }]));
785     }
786 );
787
788 export const loadVirtualMachinesAdmin = handleFirstTimeLoad(
789     async (dispatch: Dispatch<any>) => {
790         await dispatch(loadVirtualMachinesPanel());
791         dispatch(
792             setBreadcrumbs([{ label: 'Virtual Machines Admin', icon: AdminMenuIcon }])
793         );
794     }
795 );
796
797 export const loadRepositories = handleFirstTimeLoad(
798     async (dispatch: Dispatch<any>) => {
799         await dispatch(loadRepositoriesPanel());
800         dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
801     }
802 );
803
804 export const loadSshKeys = handleFirstTimeLoad(
805     async (dispatch: Dispatch<any>) => {
806         await dispatch(loadSshKeysPanel());
807     }
808 );
809
810 export const loadSiteManager = handleFirstTimeLoad(
811     async (dispatch: Dispatch<any>) => {
812         await dispatch(loadSiteManagerPanel());
813     }
814 );
815
816 export const loadUserProfile = (userUuid?: string) =>
817     handleFirstTimeLoad((dispatch: Dispatch<any>) => {
818         if (userUuid) {
819             dispatch(setUserProfileBreadcrumbs(userUuid));
820             dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
821         } else {
822             dispatch(setMyAccountBreadcrumbs());
823             dispatch(userProfilePanelActions.loadUserProfilePanel());
824         }
825     });
826
827 export const loadLinkAccount = handleFirstTimeLoad(
828     (dispatch: Dispatch<any>) => {
829         dispatch(loadLinkAccountPanel());
830     }
831 );
832
833 export const loadKeepServices = handleFirstTimeLoad(
834     async (dispatch: Dispatch<any>) => {
835         await dispatch(loadKeepServicesPanel());
836     }
837 );
838
839 export const loadUsers = handleFirstTimeLoad(
840     async (dispatch: Dispatch<any>) => {
841         await dispatch(loadUsersPanel());
842         dispatch(setUsersBreadcrumbs());
843     }
844 );
845
846 export const loadApiClientAuthorizations = handleFirstTimeLoad(
847     async (dispatch: Dispatch<any>) => {
848         await dispatch(loadApiClientAuthorizationsPanel());
849     }
850 );
851
852 export const loadGroupsPanel = handleFirstTimeLoad(
853     (dispatch: Dispatch<any>) => {
854         dispatch(setGroupsBreadcrumbs());
855         dispatch(groupPanelActions.loadGroupsPanel());
856     }
857 );
858
859 export const loadGroupDetailsPanel = (groupUuid: string) =>
860     handleFirstTimeLoad((dispatch: Dispatch<any>) => {
861         dispatch(setGroupDetailsBreadcrumbs(groupUuid));
862         dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
863     });
864
865 const finishLoadingProject =
866     (project: GroupContentsResource | string) =>
867         async (dispatch: Dispatch<any>) => {
868             const uuid = typeof project === 'string' ? project : project.uuid;
869             dispatch(openProjectPanel(uuid));
870             dispatch(loadDetailsPanel(uuid));
871             if (typeof project !== 'string') {
872                 dispatch(updateResources([project]));
873             }
874         };
875
876 const loadGroupContentsResource = async (params: {
877     uuid: string;
878     userUuid: string;
879     services: ServiceRepository;
880 }) => {
881     const filters = new FilterBuilder()
882         .addEqual('uuid', params.uuid)
883         .getFilters();
884     const { items } = await params.services.groupsService.contents(
885         params.userUuid,
886         {
887             filters,
888             recursive: true,
889             includeTrash: true,
890         }
891     );
892     const resource = items.shift();
893     let handler: GroupContentsHandler;
894     if (resource) {
895         handler =
896             (resource.kind === ResourceKind.COLLECTION ||
897                 resource.kind === ResourceKind.PROJECT) &&
898                 resource.isTrashed
899                 ? groupContentsHandlers.TRASHED(resource)
900                 : groupContentsHandlers.OWNED(resource);
901     } else {
902         const kind = extractUuidKind(params.uuid);
903         let resource: GroupContentsResource;
904         if (kind === ResourceKind.COLLECTION) {
905             resource = await params.services.collectionService.get(params.uuid);
906         } else if (kind === ResourceKind.PROJECT) {
907             resource = await params.services.projectService.get(params.uuid);
908         } else if (kind === ResourceKind.WORKFLOW) {
909             resource = await params.services.workflowService.get(params.uuid);
910         } else if (kind === ResourceKind.CONTAINER_REQUEST) {
911             resource = await params.services.containerRequestService.get(params.uuid);
912         } else {
913             throw new Error("loadGroupContentsResource unsupported kind " + kind)
914         }
915         handler = groupContentsHandlers.SHARED(resource);
916     }
917     return (
918         cases: MatchCases<
919             typeof groupContentsHandlersRecord,
920             GroupContentsHandler,
921             void
922         >
923     ) => groupContentsHandlers.match(handler, cases);
924 };
925
926 const groupContentsHandlersRecord = {
927     TRASHED: ofType<GroupContentsResource>(),
928     SHARED: ofType<GroupContentsResource>(),
929     OWNED: ofType<GroupContentsResource>(),
930 };
931
932 const groupContentsHandlers = unionize(groupContentsHandlersRecord);
933
934 type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;