20487: loadProject doesn't call openProjectPanel
[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     projectPanelActions,
17     setIsProjectPanelTrashed,
18 } from 'store/project-panel/project-panel-action';
19 import {
20     activateSidePanelTreeItem,
21     initSidePanelTree,
22     loadSidePanelTreeProjects,
23     SidePanelTreeCategory,
24 } from 'store/side-panel-tree/side-panel-tree-actions';
25 import { updateResources } from 'store/resources/resources-actions';
26 import { projectPanelColumns } from 'views/project-panel/project-panel';
27 import { favoritePanelColumns } from 'views/favorite-panel/favorite-panel';
28 import { matchRootRoute } from 'routes/routes';
29 import {
30     setBreadcrumbs,
31     setGroupDetailsBreadcrumbs,
32     setGroupsBreadcrumbs,
33     setProcessBreadcrumbs,
34     setSharedWithMeBreadcrumbs,
35     setSidePanelBreadcrumbs,
36     setTrashBreadcrumbs,
37     setUsersBreadcrumbs,
38     setMyAccountBreadcrumbs,
39     setUserProfileBreadcrumbs,
40 } from 'store/breadcrumbs/breadcrumbs-actions';
41 import {
42     navigateTo,
43     navigateToRootProject,
44 } from 'store/navigation/navigation-action';
45 import { MoveToFormDialogData } from 'store/move-to-dialog/move-to-dialog';
46 import { ServiceRepository } from 'services/services';
47 import { getResource } from 'store/resources/resources';
48 import * as projectCreateActions from 'store/projects/project-create-actions';
49 import * as projectMoveActions from 'store/projects/project-move-actions';
50 import * as projectUpdateActions from 'store/projects/project-update-actions';
51 import * as collectionCreateActions from 'store/collections/collection-create-actions';
52 import * as collectionCopyActions from 'store/collections/collection-copy-actions';
53 import * as collectionMoveActions from 'store/collections/collection-move-actions';
54 import * as processesActions from 'store/processes/processes-actions';
55 import * as processMoveActions from 'store/processes/process-move-actions';
56 import * as processUpdateActions from 'store/processes/process-update-actions';
57 import * as processCopyActions from 'store/processes/process-copy-actions';
58 import { trashPanelColumns } from 'views/trash-panel/trash-panel';
59 import {
60     loadTrashPanel,
61     trashPanelActions,
62 } from 'store/trash-panel/trash-panel-action';
63 import { loadProcessPanel } from 'store/process-panel/process-panel-actions';
64 import {
65     loadSharedWithMePanel,
66     sharedWithMePanelActions,
67 } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
68 import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog';
69 import { workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions';
70 import { loadSshKeysPanel } from 'store/auth/auth-action-ssh';
71 import {
72     loadLinkAccountPanel,
73     linkAccountPanelActions,
74 } from 'store/link-account-panel/link-account-panel-actions';
75 import { loadSiteManagerPanel } from 'store/auth/auth-action-session';
76 import { workflowPanelColumns } from 'views/workflow-panel/workflow-panel-view';
77 import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
78 import { getProgressIndicator } from 'store/progress-indicator/progress-indicator-reducer';
79 import { extractUuidKind, ResourceKind } from 'models/resource';
80 import { FilterBuilder } from 'services/api/filter-builder';
81 import { GroupContentsResource } from 'services/groups-service/groups-service';
82 import { MatchCases, ofType, unionize, UnionOf } from 'common/unionize';
83 import { loadRunProcessPanel } from 'store/run-process-panel/run-process-panel-actions';
84 import {
85     collectionPanelActions,
86     loadCollectionPanel,
87 } from 'store/collection-panel/collection-panel-action';
88 import { CollectionResource } from 'models/collection';
89 import { WorkflowResource } from 'models/workflow';
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                 let collection: CollectionResource | undefined;
456                 let breadcrumbfunc: ((uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => Promise<void>) | undefined;
457                 let sidepanel: string | undefined;
458                 match({
459                     OWNED: (thecollection) => {
460                         collection = thecollection as CollectionResource;
461                         sidepanel = collection.ownerUuid;
462                         breadcrumbfunc = setSidePanelBreadcrumbs;
463                     },
464                     SHARED: (thecollection) => {
465                         collection = thecollection as CollectionResource;
466                         sidepanel = collection.ownerUuid;
467                         breadcrumbfunc = setSharedWithMeBreadcrumbs;
468                     },
469                     TRASHED: (thecollection) => {
470                         collection = thecollection as CollectionResource;
471                         sidepanel = SidePanelTreeCategory.TRASH;
472                         breadcrumbfunc = () => setTrashBreadcrumbs('');
473                     },
474                 });
475                 if (collection && breadcrumbfunc && sidepanel) {
476                     dispatch(updateResources([collection]));
477                     await dispatch<any>(finishLoadingProject(collection.ownerUuid));
478                     dispatch(collectionPanelActions.SET_COLLECTION(collection));
479                     await dispatch(activateSidePanelTreeItem(sidepanel));
480                     dispatch(breadcrumbfunc(collection.ownerUuid));
481                     dispatch(loadCollectionPanel(collection.uuid));
482                 }
483             }
484         }
485     );
486
487 export const createCollection =
488     (data: collectionCreateActions.CollectionCreateFormDialogData) =>
489         async (dispatch: Dispatch) => {
490             const collection = await dispatch<any>(
491                 collectionCreateActions.createCollection(data)
492             );
493             if (collection) {
494                 dispatch(
495                     snackbarActions.OPEN_SNACKBAR({
496                         message: 'Collection has been successfully created.',
497                         hideDuration: 2000,
498                         kind: SnackbarKind.SUCCESS,
499                     })
500                 );
501                 dispatch<any>(updateResources([collection]));
502                 dispatch<any>(navigateTo(collection.uuid));
503             }
504         };
505
506 export const copyCollection =
507     (data: CopyFormDialogData) =>
508         async (
509             dispatch: Dispatch,
510             getState: () => RootState,
511             services: ServiceRepository
512         ) => {
513             try {
514                 const copyToProject = getResource(data.ownerUuid)(getState().resources);
515                 const collection = await dispatch<any>(
516                     collectionCopyActions.copyCollection(data)
517                 );
518                 if (copyToProject && collection) {
519                     dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
520                     dispatch(
521                         snackbarActions.OPEN_SNACKBAR({
522                             message: 'Collection has been copied.',
523                             hideDuration: 3000,
524                             kind: SnackbarKind.SUCCESS,
525                             link: collection.ownerUuid,
526                         })
527                     );
528                 }
529             } catch (e) {
530                 dispatch(
531                     snackbarActions.OPEN_SNACKBAR({
532                         message: e.message,
533                         hideDuration: 2000,
534                         kind: SnackbarKind.ERROR,
535                     })
536                 );
537             }
538         };
539
540 export const moveCollection =
541     (data: MoveToFormDialogData) =>
542         async (
543             dispatch: Dispatch,
544             getState: () => RootState,
545             services: ServiceRepository
546         ) => {
547             try {
548                 const collection = await dispatch<any>(
549                     collectionMoveActions.moveCollection(data)
550                 );
551                 dispatch<any>(updateResources([collection]));
552                 dispatch<any>(reloadProjectMatchingUuid([collection.ownerUuid]));
553                 dispatch(
554                     snackbarActions.OPEN_SNACKBAR({
555                         message: 'Collection has been moved.',
556                         hideDuration: 2000,
557                         kind: SnackbarKind.SUCCESS,
558                     })
559                 );
560             } catch (e) {
561                 dispatch(
562                     snackbarActions.OPEN_SNACKBAR({
563                         message: e.message,
564                         hideDuration: 2000,
565                         kind: SnackbarKind.ERROR,
566                     })
567                 );
568             }
569         };
570
571 export const loadProcess = (uuid: string) =>
572     handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState) => {
573         dispatch<any>(loadProcessPanel(uuid));
574         const process = await dispatch<any>(processesActions.loadProcess(uuid));
575         if (process) {
576             await dispatch<any>(finishLoadingProject(process.containerRequest.ownerUuid));
577             await dispatch<any>(
578                 activateSidePanelTreeItem(process.containerRequest.ownerUuid)
579             );
580             dispatch<any>(setProcessBreadcrumbs(uuid));
581             dispatch<any>(loadDetailsPanel(uuid));
582         }
583     });
584
585 export const loadRegisteredWorkflow = (uuid: string) =>
586     handleFirstTimeLoad(async (dispatch: Dispatch,
587         getState: () => RootState,
588         services: ServiceRepository) => {
589
590         const userUuid = getUserUuid(getState());
591         if (userUuid) {
592             const match = await loadGroupContentsResource({
593                 uuid,
594                 userUuid,
595                 services,
596             });
597             let workflow: WorkflowResource | undefined;
598             let breadcrumbfunc: ((uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => Promise<void>) | undefined;
599             match({
600                 OWNED: async (theworkflow) => {
601                     workflow = theworkflow as WorkflowResource;
602                     breadcrumbfunc = setSidePanelBreadcrumbs;
603                 },
604                 SHARED: async (theworkflow) => {
605                     workflow = theworkflow as WorkflowResource;
606                     breadcrumbfunc = setSharedWithMeBreadcrumbs;
607                 },
608                 TRASHED: () => { }
609             });
610             if (workflow && breadcrumbfunc) {
611                 dispatch(updateResources([workflow]));
612                 await dispatch<any>(finishLoadingProject(workflow.ownerUuid));
613                 await dispatch<any>(activateSidePanelTreeItem(workflow.ownerUuid));
614                 dispatch<any>(breadcrumbfunc(workflow.ownerUuid));
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(loadDetailsPanel(uuid));
869             if (typeof project !== 'string') {
870                 dispatch(updateResources([project]));
871             }
872         };
873
874 const loadGroupContentsResource = async (params: {
875     uuid: string;
876     userUuid: string;
877     services: ServiceRepository;
878 }) => {
879     const filters = new FilterBuilder()
880         .addEqual('uuid', params.uuid)
881         .getFilters();
882     const { items } = await params.services.groupsService.contents(
883         params.userUuid,
884         {
885             filters,
886             recursive: true,
887             includeTrash: true,
888         }
889     );
890     const resource = items.shift();
891     let handler: GroupContentsHandler;
892     if (resource) {
893         handler =
894             (resource.kind === ResourceKind.COLLECTION ||
895                 resource.kind === ResourceKind.PROJECT) &&
896                 resource.isTrashed
897                 ? groupContentsHandlers.TRASHED(resource)
898                 : groupContentsHandlers.OWNED(resource);
899     } else {
900         const kind = extractUuidKind(params.uuid);
901         let resource: GroupContentsResource;
902         if (kind === ResourceKind.COLLECTION) {
903             resource = await params.services.collectionService.get(params.uuid);
904         } else if (kind === ResourceKind.PROJECT) {
905             resource = await params.services.projectService.get(params.uuid);
906         } else if (kind === ResourceKind.WORKFLOW) {
907             resource = await params.services.workflowService.get(params.uuid);
908         } else if (kind === ResourceKind.CONTAINER_REQUEST) {
909             resource = await params.services.containerRequestService.get(params.uuid);
910         } else {
911             throw new Error("loadGroupContentsResource unsupported kind " + kind)
912         }
913         handler = groupContentsHandlers.SHARED(resource);
914     }
915     return (
916         cases: MatchCases<
917             typeof groupContentsHandlersRecord,
918             GroupContentsHandler,
919             void
920         >
921     ) => groupContentsHandlers.match(handler, cases);
922 };
923
924 const groupContentsHandlersRecord = {
925     TRASHED: ofType<GroupContentsResource>(),
926     SHARED: ofType<GroupContentsResource>(),
927     OWNED: ofType<GroupContentsResource>(),
928 };
929
930 const groupContentsHandlers = unionize(groupContentsHandlersRecord);
931
932 type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;