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 (uuidKind === ResourceKind.USER) {
83 // Handle another user root project
84 const user = getResource<UserResource>(uuid)(getState().resources);
86 label: (user as any)?.fullName || user?.username || uuid,
87 uuid: user?.uuid || uuid,
88 icon: getSidePanelIcon(SidePanelTreeCategory.PROJECTS)
90 } else if (Object.values(SidePanelTreeCategory).includes(uuid as SidePanelTreeCategory)) {
91 // Handle SidePanelTreeCategory root
95 icon: getSidePanelIcon(uuid)
99 breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
100 ancestor.kind === ResourceKind.GROUP
101 ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
105 if (uuidKind === ResourceKind.COLLECTION) {
106 const collectionItem = item ? item : await services.collectionService.get(currentUuid);
107 const parentProcessItem = await getCollectionParent(collectionItem)(services);
108 if (parentProcessItem) {
109 const mainProcessItem = await getProcessParent(parentProcessItem)(services);
110 mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
111 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
113 dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
114 } else if (uuidKind === ResourceKind.PROCESS) {
115 const processItem = await services.containerRequestService.get(currentUuid);
116 const parentProcessItem = await getProcessParent(processItem)(services);
117 if (parentProcessItem) {
118 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
120 dispatch(setBreadcrumbs(breadcrumbs, processItem));
121 } else if (uuidKind === ResourceKind.WORKFLOW) {
122 const workflowItem = await services.workflowService.get(currentUuid);
123 dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
125 dispatch(setBreadcrumbs(breadcrumbs));
127 console.log("Error setting breadcrumbs "+e);
129 dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
133 export const setSharedWithMeBreadcrumbs = (uuid: string) =>
134 setCategoryBreadcrumbs(uuid, SidePanelTreeCategory.SHARED_WITH_ME);
136 export const setTrashBreadcrumbs = (uuid: string) =>
137 setCategoryBreadcrumbs(uuid, SidePanelTreeCategory.TRASH);
139 export const setCategoryBreadcrumbs = (uuid: string, category: string) =>
140 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
142 dispatch(progressIndicatorActions.START_WORKING(uuid + "-breadcrumbs"));
143 const ancestors = await services.ancestorsService.ancestors(uuid, '');
144 dispatch(updateResources(ancestors));
145 const initialBreadcrumbs: Breadcrumb[] = [
149 icon: getSidePanelIcon(category)
152 const { collectionPanel: { item } } = getState();
153 const path = getState().router.location!.pathname;
154 const currentUuid = path.split('/')[2];
155 const uuidKind = extractUuidKind(currentUuid);
156 let breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
157 ancestor.kind === ResourceKind.GROUP
158 ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
161 if (uuidKind === ResourceKind.COLLECTION) {
162 const collectionItem = item ? item : await services.collectionService.get(currentUuid);
163 const parentProcessItem = await getCollectionParent(collectionItem)(services);
164 if (parentProcessItem) {
165 const mainProcessItem = await getProcessParent(parentProcessItem)(services);
166 mainProcessItem && breadcrumbs.push(resourceToBreadcrumb(mainProcessItem));
167 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
169 dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
170 } else if (uuidKind === ResourceKind.PROCESS) {
171 const processItem = await services.containerRequestService.get(currentUuid);
172 const parentProcessItem = await getProcessParent(processItem)(services);
173 if (parentProcessItem) {
174 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
176 dispatch(setBreadcrumbs(breadcrumbs, processItem));
177 } else if (uuidKind === ResourceKind.WORKFLOW) {
178 const workflowItem = await services.workflowService.get(currentUuid);
179 dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
181 dispatch(setBreadcrumbs(breadcrumbs));
183 dispatch(progressIndicatorActions.STOP_WORKING(uuid + "-breadcrumbs"));
187 const getProcessParent = (childProcess: ContainerRequestResource) =>
188 async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
189 if (childProcess.requestingContainerUuid) {
190 const parentProcesses = await services.containerRequestService.list({
191 order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
192 filters: new FilterBuilder().addEqual('container_uuid', childProcess.requestingContainerUuid).getFilters(),
193 select: containerRequestFieldsNoMounts,
195 if (parentProcesses.items.length > 0) {
196 return parentProcesses.items[0];
205 const getCollectionParent = (collection: CollectionResource) =>
206 async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
207 const parentOutputPromise = services.containerRequestService.list({
208 order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
209 filters: new FilterBuilder().addEqual('output_uuid', collection.uuid).getFilters(),
210 select: containerRequestFieldsNoMounts,
212 const parentLogPromise = services.containerRequestService.list({
213 order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
214 filters: new FilterBuilder().addEqual('log_uuid', collection.uuid).getFilters(),
215 select: containerRequestFieldsNoMounts,
217 const [parentOutput, parentLog] = await Promise.all([parentOutputPromise, parentLogPromise]);
218 return parentOutput.items.length > 0 ?
219 parentOutput.items[0] :
220 parentLog.items.length > 0 ?
226 export const setProjectBreadcrumbs = (uuid: string) =>
227 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
228 const ancestors = await services.ancestorsService.ancestors(uuid, '');
229 const rootUuid = getUserUuid(getState());
230 if (uuid === rootUuid || ancestors.find(ancestor => ancestor.uuid === rootUuid)) {
231 dispatch(setSidePanelBreadcrumbs(uuid));
233 dispatch(setSharedWithMeBreadcrumbs(uuid));
234 dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
238 export const setProcessBreadcrumbs = (processUuid: string) =>
239 (dispatch: Dispatch, getState: () => RootState) => {
240 const { resources } = getState();
241 const process = getProcess(processUuid)(resources);
243 dispatch<any>(setProjectBreadcrumbs(process.containerRequest.ownerUuid));
247 export const setGroupsBreadcrumbs = () =>
249 label: SidePanelTreeCategory.GROUPS,
250 uuid: SidePanelTreeCategory.GROUPS,
251 icon: getSidePanelIcon(SidePanelTreeCategory.GROUPS)
254 export const setGroupDetailsBreadcrumbs = (groupUuid: string) =>
255 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
257 const group = getResource<GroupResource>(groupUuid)(getState().resources);
259 const breadcrumbs: Breadcrumb[] = [
261 label: SidePanelTreeCategory.GROUPS,
262 uuid: SidePanelTreeCategory.GROUPS,
263 icon: getSidePanelIcon(SidePanelTreeCategory.GROUPS)
265 { label: group ? group.name : (await services.groupsService.get(groupUuid)).name, uuid: groupUuid },
268 dispatch(setBreadcrumbs(breadcrumbs));
272 export const USERS_PANEL_LABEL = 'Users';
274 export const setUsersBreadcrumbs = () =>
275 setBreadcrumbs([{ label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL }]);
277 export const setUserProfileBreadcrumbs = (userUuid: string) =>
278 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
280 const user = getResource<UserResource>(userUuid)(getState().resources)
281 || (await services.userService.get(userUuid, false));
282 const userProfileBreadcrumbs: Breadcrumb[] = [
283 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
284 { label: user ? `${user.firstName} ${user.lastName}` : userUuid, uuid: userUuid },
286 dispatch(setBreadcrumbs(userProfileBreadcrumbs));
288 const breadcrumbs: Breadcrumb[] = [
289 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
290 { label: userUuid, uuid: userUuid },
292 dispatch(setBreadcrumbs(breadcrumbs));
296 export const MY_ACCOUNT_PANEL_LABEL = 'My Account';
298 export const setMyAccountBreadcrumbs = () =>
299 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
300 dispatch(setBreadcrumbs([
301 { label: MY_ACCOUNT_PANEL_LABEL, uuid: MY_ACCOUNT_PANEL_LABEL },
305 export const INSTANCE_TYPES_PANEL_LABEL = 'Instance Types';
307 export const setInstanceTypesBreadcrumbs = () =>
308 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
309 dispatch(setBreadcrumbs([
310 { label: INSTANCE_TYPES_PANEL_LABEL, uuid: INSTANCE_TYPES_PANEL_LABEL, icon: ResourceIcon },
314 export const setVirtualMachinesBreadcrumbs = () =>
315 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
316 dispatch(setBreadcrumbs([
317 { label: SidePanelTreeCategory.SHELL_ACCESS, uuid: SidePanelTreeCategory.SHELL_ACCESS, icon: TerminalIcon },
321 export const VIRTUAL_MACHINES_ADMIN_PANEL_LABEL = 'Shell Access Admin';
323 export const setVirtualMachinesAdminBreadcrumbs = () =>
324 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
325 dispatch(setBreadcrumbs([
326 { label: VIRTUAL_MACHINES_ADMIN_PANEL_LABEL, uuid: VIRTUAL_MACHINES_ADMIN_PANEL_LABEL, icon: AdminMenuIcon },
330 export const REPOSITORIES_PANEL_LABEL = 'Repositories';
332 export const setRepositoriesBreadcrumbs = () =>
333 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
334 dispatch(setBreadcrumbs([
335 { label: REPOSITORIES_PANEL_LABEL, uuid: REPOSITORIES_PANEL_LABEL, icon: AdminMenuIcon },