1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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 { AdminMenuIcon, CollectionIcon, IconType, ProcessIcon, ProjectIcon, ResourceIcon, TerminalIcon, 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";
29 export const BREADCRUMBS = 'breadcrumbs';
31 export const setBreadcrumbs = (breadcrumbs: any, currentItem?: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource) => {
33 const currentCrumb = resourceToBreadcrumb(currentItem)
34 if (currentCrumb.label.length) breadcrumbs.push(currentCrumb);
36 return propertiesActions.SET_PROPERTY({ key: BREADCRUMBS, value: breadcrumbs });
39 const resourceToBreadcrumbIcon = (resource: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource): IconType | undefined => {
40 switch (resource.kind) {
41 case ResourceKind.PROJECT:
43 case ResourceKind.PROCESS:
45 case ResourceKind.COLLECTION:
46 return CollectionIcon;
47 case ResourceKind.WORKFLOW:
54 const resourceToBreadcrumb = (resource: (CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource) & {fullName?: string} ): Breadcrumb => ({
55 label: resource.name || resource.fullName || '',
57 icon: resourceToBreadcrumbIcon(resource),
60 export const setSidePanelBreadcrumbs = (uuid: string) =>
61 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
63 dispatch(progressIndicatorActions.START_WORKING(uuid + "-breadcrumbs"));
64 const ancestors = await services.ancestorsService.ancestors(uuid, '');
65 dispatch(updateResources(ancestors));
67 let breadcrumbs: Breadcrumb[] = [];
68 const { collectionPanel: { item } } = getState();
70 const path = getState().router.location!.pathname;
71 const currentUuid = path.split('/')[2];
72 const uuidKind = extractUuidKind(currentUuid);
73 const rootUuid = getUserUuid(getState());
75 if (ancestors.find(ancestor => ancestor.uuid === rootUuid)) {
76 // Handle home project uuid root
78 label: SidePanelTreeCategory.PROJECTS,
79 uuid: SidePanelTreeCategory.PROJECTS,
80 icon: getSidePanelIcon(SidePanelTreeCategory.PROJECTS)
82 } else if (Object.values(SidePanelTreeCategory).includes(uuid as SidePanelTreeCategory)) {
83 // Handle SidePanelTreeCategory root
87 icon: getSidePanelIcon(uuid)
91 breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
92 ancestor.kind === ResourceKind.GROUP
93 ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
97 if (uuidKind === ResourceKind.COLLECTION) {
98 const collectionItem = item ? item : await services.collectionService.get(currentUuid);
99 const parentProcessItem = await getCollectionParent(collectionItem)(services);
100 if (parentProcessItem) {
101 const mainProcessItem = await getProcessParent(parentProcessItem)(services);
102 mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
103 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
105 dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
106 } else if (uuidKind === ResourceKind.PROCESS) {
107 const processItem = await services.containerRequestService.get(currentUuid);
108 const parentProcessItem = await getProcessParent(processItem)(services);
109 if (parentProcessItem) {
110 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
112 dispatch(setBreadcrumbs(breadcrumbs, processItem));
113 } else if (uuidKind === ResourceKind.WORKFLOW) {
114 const workflowItem = await services.workflowService.get(currentUuid);
115 dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
117 dispatch(setBreadcrumbs(breadcrumbs));
119 dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
123 export const setSharedWithMeBreadcrumbs = (uuid: string) =>
124 setCategoryBreadcrumbs(uuid, SidePanelTreeCategory.SHARED_WITH_ME);
126 export const setTrashBreadcrumbs = (uuid: string) =>
127 setCategoryBreadcrumbs(uuid, SidePanelTreeCategory.TRASH);
129 export const setCategoryBreadcrumbs = (uuid: string, category: string) =>
130 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
132 dispatch(progressIndicatorActions.START_WORKING(uuid + "-breadcrumbs"));
133 const ancestors = await services.ancestorsService.ancestors(uuid, '');
134 dispatch(updateResources(ancestors));
135 const initialBreadcrumbs: Breadcrumb[] = [
139 icon: getSidePanelIcon(category)
142 const { collectionPanel: { item } } = getState();
143 const path = getState().router.location!.pathname;
144 const currentUuid = path.split('/')[2];
145 const uuidKind = extractUuidKind(currentUuid);
146 let breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
147 ancestor.kind === ResourceKind.GROUP
148 ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
151 if (uuidKind === ResourceKind.COLLECTION) {
152 const collectionItem = item ? item : await services.collectionService.get(currentUuid);
153 const parentProcessItem = await getCollectionParent(collectionItem)(services);
154 if (parentProcessItem) {
155 const mainProcessItem = await getProcessParent(parentProcessItem)(services);
156 mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
157 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
159 dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
160 } else if (uuidKind === ResourceKind.PROCESS) {
161 const processItem = await services.containerRequestService.get(currentUuid);
162 const parentProcessItem = await getProcessParent(processItem)(services);
163 if (parentProcessItem) {
164 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
166 dispatch(setBreadcrumbs(breadcrumbs, processItem));
167 } else if (uuidKind === ResourceKind.WORKFLOW) {
168 const workflowItem = await services.workflowService.get(currentUuid);
169 dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
171 dispatch(setBreadcrumbs(breadcrumbs));
173 dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
177 const getProcessParent = (childProcess: ContainerRequestResource) =>
178 async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
179 if (childProcess.requestingContainerUuid) {
180 const parentProcesses = await services.containerRequestService.list({
181 order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
182 filters: new FilterBuilder().addEqual('container_uuid', childProcess.requestingContainerUuid).getFilters(),
183 select: containerRequestFieldsNoMounts,
185 if (parentProcesses.items.length > 0) {
186 return parentProcesses.items[0];
195 const getCollectionParent = (collection: CollectionResource) =>
196 async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
197 const parentOutputPromise = services.containerRequestService.list({
198 order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
199 filters: new FilterBuilder().addEqual('output_uuid', collection.uuid).getFilters(),
200 select: containerRequestFieldsNoMounts,
202 const parentLogPromise = services.containerRequestService.list({
203 order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
204 filters: new FilterBuilder().addEqual('log_uuid', collection.uuid).getFilters(),
205 select: containerRequestFieldsNoMounts,
207 const [parentOutput, parentLog] = await Promise.all([parentOutputPromise, parentLogPromise]);
208 return parentOutput.items.length > 0 ?
209 parentOutput.items[0] :
210 parentLog.items.length > 0 ?
216 export const setProjectBreadcrumbs = (uuid: string) =>
217 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
218 const ancestors = await services.ancestorsService.ancestors(uuid, '');
219 const rootUuid = getUserUuid(getState());
220 if (uuid === rootUuid || ancestors.find(ancestor => ancestor.uuid === rootUuid)) {
221 dispatch(setSidePanelBreadcrumbs(uuid));
223 dispatch(setSharedWithMeBreadcrumbs(uuid));
224 dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
228 export const setProcessBreadcrumbs = (processUuid: string) =>
229 (dispatch: Dispatch, getState: () => RootState) => {
230 const { resources } = getState();
231 const process = getProcess(processUuid)(resources);
233 dispatch<any>(setProjectBreadcrumbs(process.containerRequest.ownerUuid));
237 export const setGroupsBreadcrumbs = () =>
239 label: SidePanelTreeCategory.GROUPS,
240 uuid: SidePanelTreeCategory.GROUPS,
241 icon: getSidePanelIcon(SidePanelTreeCategory.GROUPS)
244 export const setGroupDetailsBreadcrumbs = (groupUuid: string) =>
245 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
247 const group = getResource<GroupResource>(groupUuid)(getState().resources);
249 const breadcrumbs: Breadcrumb[] = [
251 label: SidePanelTreeCategory.GROUPS,
252 uuid: SidePanelTreeCategory.GROUPS,
253 icon: getSidePanelIcon(SidePanelTreeCategory.GROUPS)
255 { label: group ? group.name : (await services.groupsService.get(groupUuid)).name, uuid: groupUuid },
258 dispatch(setBreadcrumbs(breadcrumbs));
262 export const USERS_PANEL_LABEL = 'Users';
264 export const setUsersBreadcrumbs = () =>
265 setBreadcrumbs([{ label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL }]);
267 export const setUserProfileBreadcrumbs = (userUuid: string) =>
268 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
270 const user = getResource<UserResource>(userUuid)(getState().resources)
271 || await services.userService.get(userUuid, false);
272 const currentCrumbs = getState().properties.breadcrumbs as Breadcrumb[]
273 const userProfileBreadcrumbs: Breadcrumb[] = [
274 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
275 { label: user ? `${user.firstName} ${user.lastName}` : userUuid, uuid: userUuid },
277 const breadcrumbsWithPreviousCrumbs: Breadcrumb[] = [
279 { label: user ? `${user.firstName} ${user.lastName}` : userUuid, uuid: userUuid },
281 dispatch(setBreadcrumbs(currentCrumbs.some((crumb) => crumb.label === SidePanelTreeCategory.GROUPS) ? breadcrumbsWithPreviousCrumbs : userProfileBreadcrumbs));
283 const breadcrumbs: Breadcrumb[] = [
284 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
285 { label: userUuid, uuid: userUuid },
287 dispatch(setBreadcrumbs(breadcrumbs));
291 export const MY_ACCOUNT_PANEL_LABEL = 'My Account';
293 export const setMyAccountBreadcrumbs = () =>
294 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
295 dispatch(setBreadcrumbs([
296 { label: MY_ACCOUNT_PANEL_LABEL, uuid: MY_ACCOUNT_PANEL_LABEL },
300 export const INSTANCE_TYPES_PANEL_LABEL = 'Instance Types';
302 export const setInstanceTypesBreadcrumbs = () =>
303 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
304 dispatch(setBreadcrumbs([
305 { label: INSTANCE_TYPES_PANEL_LABEL, uuid: INSTANCE_TYPES_PANEL_LABEL, icon: ResourceIcon },
309 export const setVirtualMachinesBreadcrumbs = () =>
310 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
311 dispatch(setBreadcrumbs([
312 { label: SidePanelTreeCategory.SHELL_ACCESS, uuid: SidePanelTreeCategory.SHELL_ACCESS, icon: TerminalIcon },
316 export const VIRTUAL_MACHINES_ADMIN_PANEL_LABEL = 'Shell Access Admin';
318 export const setVirtualMachinesAdminBreadcrumbs = () =>
319 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
320 dispatch(setBreadcrumbs([
321 { label: VIRTUAL_MACHINES_ADMIN_PANEL_LABEL, uuid: VIRTUAL_MACHINES_ADMIN_PANEL_LABEL, icon: AdminMenuIcon },
325 export const REPOSITORIES_PANEL_LABEL = 'Repositories';
327 export const setRepositoriesBreadcrumbs = () =>
328 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
329 dispatch(setBreadcrumbs([
330 { label: REPOSITORIES_PANEL_LABEL, uuid: REPOSITORIES_PANEL_LABEL, icon: AdminMenuIcon },