Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / src / store / breadcrumbs / breadcrumbs-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 { getResource } from 'store/resources/resources';
9 import { propertiesActions } from '../properties/properties-actions';
10 import { getProcess } from 'store/processes/process';
11 import { ServiceRepository } from 'services/services';
12 import { SidePanelTreeCategory, activateSidePanelTreeItem } from 'store/side-panel-tree/side-panel-tree-actions';
13 import { updateResources } from '../resources/resources-actions';
14 import { ResourceKind } from 'models/resource';
15 import { GroupResource } from 'models/group';
16 import { extractUuidKind } from 'models/resource';
17 import { UserResource } from 'models/user';
18 import { FilterBuilder } from 'services/api/filter-builder';
19 import { ProcessResource } from 'models/process';
20 import { OrderBuilder } from 'services/api/order-builder';
21 import { Breadcrumb } from 'components/breadcrumbs/breadcrumbs';
22 import { ContainerRequestResource, containerRequestFieldsNoMounts } from 'models/container-request';
23 import { CollectionIcon, IconType, ProcessIcon, ProjectIcon, WorkflowIcon } from 'components/icon/icon';
24 import { CollectionResource } from 'models/collection';
25 import { getSidePanelIcon } from 'views-components/side-panel-tree/side-panel-tree';
26 import { WorkflowResource } from 'models/workflow';
27 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
28
29 export const BREADCRUMBS = 'breadcrumbs';
30
31 export const setBreadcrumbs = (breadcrumbs: any, currentItem?: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource) => {
32     if (currentItem) {
33         breadcrumbs.push(resourceToBreadcrumb(currentItem));
34     }
35     return propertiesActions.SET_PROPERTY({ key: BREADCRUMBS, value: breadcrumbs });
36 };
37
38 const resourceToBreadcrumbIcon = (resource: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource): IconType | undefined => {
39     switch (resource.kind) {
40         case ResourceKind.PROJECT:
41             return ProjectIcon;
42         case ResourceKind.PROCESS:
43             return ProcessIcon;
44         case ResourceKind.COLLECTION:
45             return CollectionIcon;
46         case ResourceKind.WORKFLOW:
47             return WorkflowIcon;
48         default:
49             return undefined;
50     }
51 }
52
53 const resourceToBreadcrumb = (resource: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource): Breadcrumb => ({
54     label: resource.name,
55     uuid: resource.uuid,
56     icon: resourceToBreadcrumbIcon(resource),
57 })
58
59 export const setSidePanelBreadcrumbs = (uuid: string) =>
60     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
61         try {
62             dispatch(progressIndicatorActions.START_WORKING(uuid + "-breadcrumbs"));
63             const ancestors = await services.ancestorsService.ancestors(uuid, '');
64             dispatch(updateResources(ancestors));
65
66             let breadcrumbs: Breadcrumb[] = [];
67             const { collectionPanel: { item } } = getState();
68
69             const path = getState().router.location!.pathname;
70             const currentUuid = path.split('/')[2];
71             const uuidKind = extractUuidKind(currentUuid);
72             const rootUuid = getUserUuid(getState());
73
74             if (ancestors.find(ancestor => ancestor.uuid === rootUuid)) {
75                 // Handle home project uuid root
76                 breadcrumbs.push({
77                     label: SidePanelTreeCategory.PROJECTS,
78                     uuid: SidePanelTreeCategory.PROJECTS,
79                     icon: getSidePanelIcon(SidePanelTreeCategory.PROJECTS)
80                 });
81             } else if (Object.values(SidePanelTreeCategory).includes(uuid as SidePanelTreeCategory)) {
82                 // Handle SidePanelTreeCategory root
83                 breadcrumbs.push({
84                     label: uuid,
85                     uuid: uuid,
86                     icon: getSidePanelIcon(uuid)
87                 });
88             }
89
90             breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
91                 ancestor.kind === ResourceKind.GROUP
92                     ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
93                     : breadcrumbs,
94                 breadcrumbs);
95
96             if (uuidKind === ResourceKind.COLLECTION) {
97                 const collectionItem = item ? item : await services.collectionService.get(currentUuid);
98                 const parentProcessItem = await getCollectionParent(collectionItem)(services);
99                 if (parentProcessItem) {
100                     const mainProcessItem = await getProcessParent(parentProcessItem)(services);
101                     mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
102                     breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
103                 }
104                 dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
105             } else if (uuidKind === ResourceKind.PROCESS) {
106                 const processItem = await services.containerRequestService.get(currentUuid);
107                 const parentProcessItem = await getProcessParent(processItem)(services);
108                 if (parentProcessItem) {
109                     breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
110                 }
111                 dispatch(setBreadcrumbs(breadcrumbs, processItem));
112             } else if (uuidKind === ResourceKind.WORKFLOW) {
113                 const workflowItem = await services.workflowService.get(currentUuid);
114                 dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
115             }
116             dispatch(setBreadcrumbs(breadcrumbs));
117         } finally {
118             dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
119         }
120     };
121
122 export const setSharedWithMeBreadcrumbs = (uuid: string) =>
123     setCategoryBreadcrumbs(uuid, SidePanelTreeCategory.SHARED_WITH_ME);
124
125 export const setTrashBreadcrumbs = (uuid: string) =>
126     setCategoryBreadcrumbs(uuid, SidePanelTreeCategory.TRASH);
127
128 export const setCategoryBreadcrumbs = (uuid: string, category: string) =>
129     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
130         try {
131             dispatch(progressIndicatorActions.START_WORKING(uuid + "-breadcrumbs"));
132             const ancestors = await services.ancestorsService.ancestors(uuid, '');
133             dispatch(updateResources(ancestors));
134             const initialBreadcrumbs: Breadcrumb[] = [
135                 {
136                     label: category,
137                     uuid: category,
138                     icon: getSidePanelIcon(category)
139                 }
140             ];
141             const { collectionPanel: { item } } = getState();
142             const path = getState().router.location!.pathname;
143             const currentUuid = path.split('/')[2];
144             const uuidKind = extractUuidKind(currentUuid);
145             let breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
146                 ancestor.kind === ResourceKind.GROUP
147                     ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
148                     : breadcrumbs,
149                 initialBreadcrumbs);
150             if (uuidKind === ResourceKind.COLLECTION) {
151                 const collectionItem = item ? item : await services.collectionService.get(currentUuid);
152                 const parentProcessItem = await getCollectionParent(collectionItem)(services);
153                 if (parentProcessItem) {
154                     const mainProcessItem = await getProcessParent(parentProcessItem)(services);
155                     mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
156                     breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
157                 }
158                 dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
159             } else if (uuidKind === ResourceKind.PROCESS) {
160                 const processItem = await services.containerRequestService.get(currentUuid);
161                 const parentProcessItem = await getProcessParent(processItem)(services);
162                 if (parentProcessItem) {
163                     breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
164                 }
165                 dispatch(setBreadcrumbs(breadcrumbs, processItem));
166             } else if (uuidKind === ResourceKind.WORKFLOW) {
167                 const workflowItem = await services.workflowService.get(currentUuid);
168                 dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
169             }
170             dispatch(setBreadcrumbs(breadcrumbs));
171         } finally {
172             dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
173         }
174     };
175
176 const getProcessParent = (childProcess: ContainerRequestResource) =>
177     async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
178         if (childProcess.requestingContainerUuid) {
179             const parentProcesses = await services.containerRequestService.list({
180                 order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
181                 filters: new FilterBuilder().addEqual('container_uuid', childProcess.requestingContainerUuid).getFilters(),
182                 select: containerRequestFieldsNoMounts,
183             });
184             if (parentProcesses.items.length > 0) {
185                 return parentProcesses.items[0];
186             } else {
187                 return undefined;
188             }
189         } else {
190             return undefined;
191         }
192     }
193
194 const getCollectionParent = (collection: CollectionResource) =>
195     async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
196         const parentOutputPromise = services.containerRequestService.list({
197             order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
198             filters: new FilterBuilder().addEqual('output_uuid', collection.uuid).getFilters(),
199             select: containerRequestFieldsNoMounts,
200         });
201         const parentLogPromise = services.containerRequestService.list({
202             order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
203             filters: new FilterBuilder().addEqual('log_uuid', collection.uuid).getFilters(),
204             select: containerRequestFieldsNoMounts,
205         });
206         const [parentOutput, parentLog] = await Promise.all([parentOutputPromise, parentLogPromise]);
207         return parentOutput.items.length > 0 ?
208             parentOutput.items[0] :
209             parentLog.items.length > 0 ?
210                 parentLog.items[0] :
211                 undefined;
212     }
213
214
215 export const setProjectBreadcrumbs = (uuid: string) =>
216     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
217         const ancestors = await services.ancestorsService.ancestors(uuid, '');
218         const rootUuid = getUserUuid(getState());
219         if (uuid === rootUuid || ancestors.find(ancestor => ancestor.uuid === rootUuid)) {
220             dispatch(setSidePanelBreadcrumbs(uuid));
221         } else {
222             dispatch(setSharedWithMeBreadcrumbs(uuid));
223             dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
224         }
225     };
226
227 export const setProcessBreadcrumbs = (processUuid: string) =>
228     (dispatch: Dispatch, getState: () => RootState) => {
229         const { resources } = getState();
230         const process = getProcess(processUuid)(resources);
231         if (process) {
232             dispatch<any>(setProjectBreadcrumbs(process.containerRequest.ownerUuid));
233         }
234     };
235
236 export const setGroupsBreadcrumbs = () =>
237     setBreadcrumbs([{
238         label: SidePanelTreeCategory.GROUPS,
239         uuid: SidePanelTreeCategory.GROUPS,
240         icon: getSidePanelIcon(SidePanelTreeCategory.GROUPS)
241     }]);
242
243 export const setGroupDetailsBreadcrumbs = (groupUuid: string) =>
244     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
245
246         const group = getResource<GroupResource>(groupUuid)(getState().resources);
247
248         const breadcrumbs: Breadcrumb[] = [
249             {
250                 label: SidePanelTreeCategory.GROUPS,
251                 uuid: SidePanelTreeCategory.GROUPS,
252                 icon: getSidePanelIcon(SidePanelTreeCategory.GROUPS)
253             },
254             { label: group ? group.name : (await services.groupsService.get(groupUuid)).name, uuid: groupUuid },
255         ];
256
257         dispatch(setBreadcrumbs(breadcrumbs));
258
259     };
260
261 export const USERS_PANEL_LABEL = 'Users';
262
263 export const setUsersBreadcrumbs = () =>
264     setBreadcrumbs([{ label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL }]);
265
266 export const setUserProfileBreadcrumbs = (userUuid: string) =>
267     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
268         try {
269             const user = getResource<UserResource>(userUuid)(getState().resources)
270                 || await services.userService.get(userUuid, false);
271             const breadcrumbs: Breadcrumb[] = [
272                 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
273                 { label: user ? user.username : userUuid, uuid: userUuid },
274             ];
275             dispatch(setBreadcrumbs(breadcrumbs));
276         } catch (e) {
277             const breadcrumbs: Breadcrumb[] = [
278                 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
279                 { label: userUuid, uuid: userUuid },
280             ];
281             dispatch(setBreadcrumbs(breadcrumbs));
282         }
283     };
284
285 export const MY_ACCOUNT_PANEL_LABEL = 'My Account';
286
287 export const setMyAccountBreadcrumbs = () =>
288     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
289         dispatch(setBreadcrumbs([
290             { label: MY_ACCOUNT_PANEL_LABEL, uuid: MY_ACCOUNT_PANEL_LABEL },
291         ]));
292     };