19504: Add process / output collection parent process & resource icons to breadcrumbs
authorStephen Smith <stephen@curii.com>
Tue, 15 Nov 2022 14:43:01 +0000 (09:43 -0500)
committerStephen Smith <stephen@curii.com>
Tue, 15 Nov 2022 14:43:01 +0000 (09:43 -0500)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

src/components/breadcrumbs/breadcrumbs.tsx
src/components/icon/icon.tsx
src/store/breadcrumbs/breadcrumbs-actions.ts
src/views-components/breadcrumbs/breadcrumbs.ts

index 717966611949c8858548f9206663296139b959f3..0348b8144a5b920e92d8b884ba186229cc8a6917 100644 (file)
@@ -9,12 +9,12 @@ import { withStyles } from '@material-ui/core';
 import { IllegalNamingWarning } from '../warning/warning';
 import { IconType, FreezeIcon } from 'components/icon/icon';
 import grey from '@material-ui/core/colors/grey';
-import { ResourceBreadcrumb } from 'store/breadcrumbs/breadcrumbs-actions';
 import { ResourcesState } from 'store/resources/resources';
 
 export interface Breadcrumb {
     label: string;
     icon?: IconType;
+    uuid: string;
 }
 
 type CssRules = "item" | "currentItem" | "label" | "icon" | "frozenIcon";
@@ -42,10 +42,10 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
 });
 
 export interface BreadcrumbsProps {
-    items: ResourceBreadcrumb[];
+    items: Breadcrumb[];
     resources: ResourcesState;
-    onClick: (breadcrumb: ResourceBreadcrumb) => void;
-    onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: ResourceBreadcrumb) => void;
+    onClick: (breadcrumb: Breadcrumb) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: Breadcrumb) => void;
 }
 
 export const Breadcrumbs = withStyles(styles)(
index a6c118fc57bb07a3c21eb1d72948a441a5623d93..7fd32e5438279e55aab8be89f3072f418151e977 100644 (file)
@@ -56,6 +56,7 @@ import RestoreFromTrash from '@material-ui/icons/History';
 import Search from '@material-ui/icons/Search';
 import SettingsApplications from '@material-ui/icons/SettingsApplications';
 import SettingsEthernet from '@material-ui/icons/SettingsEthernet';
+import Settings from '@material-ui/icons/Settings';
 import Star from '@material-ui/icons/Star';
 import StarBorder from '@material-ui/icons/StarBorder';
 import Warning from '@material-ui/icons/Warning';
@@ -216,3 +217,4 @@ export const ActiveIcon: IconType = (props) => <CheckCircleOutline {...props} />
 export const SetupIcon: IconType = (props) => <RemoveCircleOutline {...props} />;
 export const InactiveIcon: IconType = (props) => <NotInterested {...props} />;
 export const ImageIcon: IconType = (props) => <Image {...props} />;
+export const ProcessBreadcrumbIcon: IconType = (props) => <Settings {...props} />;
index 08e1a132fd12e23f722cb2bc4e88f6c0374caab0..65b2870ba1f9e454755c34f4daa3ef4ed6fc306c 100644 (file)
@@ -5,7 +5,6 @@
 import { Dispatch } from 'redux';
 import { RootState } from 'store/store';
 import { getUserUuid } from "common/getuser";
-import { Breadcrumb } from 'components/breadcrumbs/breadcrumbs';
 import { getResource } from 'store/resources/resources';
 import { TreePicker } from '../tree-picker/tree-picker';
 import { getSidePanelTreeBranch, getSidePanelTreeNodeAncestorsIds } from '../side-panel-tree/side-panel-tree-actions';
@@ -18,28 +17,56 @@ import { ResourceKind } from 'models/resource';
 import { GroupResource } from 'models/group';
 import { extractUuidKind } from 'models/resource';
 import { UserResource } from 'models/user';
+import { containerRequestFieldsNoMounts } from 'store/all-processes-panel/all-processes-panel-middleware-service';
+import { FilterBuilder } from 'services/api/filter-builder';
+import { ProcessResource } from 'models/process';
+import { OrderBuilder } from 'services/api/order-builder';
+import { Breadcrumb } from 'components/breadcrumbs/breadcrumbs';
+import { ContainerRequestResource } from 'models/container-request';
+import { CollectionIcon, IconType, ProcessBreadcrumbIcon, ProjectIcon } from 'components/icon/icon';
+import { CollectionResource } from 'models/collection';
 
 export const BREADCRUMBS = 'breadcrumbs';
 
-export interface ResourceBreadcrumb extends Breadcrumb {
-    uuid: string;
-}
-
-export const setBreadcrumbs = (breadcrumbs: any, currentItem?: any) => {
+export const setBreadcrumbs = (breadcrumbs: any, currentItem?: CollectionResource | ContainerRequestResource | GroupResource) => {
     if (currentItem) {
-        const addLastItem = { label: currentItem.name, uuid: currentItem.uuid };
-        breadcrumbs.push(addLastItem);
+        breadcrumbs.push(resourceToBreadcrumb(currentItem));
     }
     return propertiesActions.SET_PROPERTY({ key: BREADCRUMBS, value: breadcrumbs });
 };
 
+const resourceToBreadcrumbIcon = (resource: CollectionResource | ContainerRequestResource | GroupResource): IconType | undefined => {
+    switch (resource.kind) {
+        case ResourceKind.PROJECT:
+            return ProjectIcon;
+            break;
+
+        case ResourceKind.PROCESS:
+            return ProcessBreadcrumbIcon;
+            break;
+
+        case ResourceKind.COLLECTION:
+            return CollectionIcon;
+            break;
+
+        default:
+            return undefined;
+            break;
+    }
+}
+
+const resourceToBreadcrumb = (resource: CollectionResource | ContainerRequestResource | GroupResource) => ({
+    label: resource.name,
+    uuid: resource.uuid,
+    icon: resourceToBreadcrumbIcon(resource),
+})
 
-const getSidePanelTreeBreadcrumbs = (uuid: string) => (treePicker: TreePicker): ResourceBreadcrumb[] => {
+const getSidePanelTreeBreadcrumbs = (uuid: string) => (treePicker: TreePicker): Breadcrumb[] => {
     const nodes = getSidePanelTreeBranch(uuid)(treePicker);
     return nodes.map(node =>
         typeof node.value === 'string'
             ? { label: node.value, uuid: node.id }
-            : { label: node.value.name, uuid: node.value.uuid });
+            : resourceToBreadcrumb(node.value));
 };
 
 export const setSidePanelBreadcrumbs = (uuid: string) =>
@@ -52,9 +79,17 @@ export const setSidePanelBreadcrumbs = (uuid: string) =>
 
         if (uuidKind === ResourceKind.COLLECTION) {
             const collectionItem = item ? item : await services.collectionService.get(currentUuid);
+            const parentProcessItem = await getCollectionParent(collectionItem)(services);
+            if (parentProcessItem) {
+                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+            }
             dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
         } else if (uuidKind === ResourceKind.PROCESS) {
             const processItem = await services.containerRequestService.get(currentUuid);
+            const parentProcessItem = await getProcessParent(processItem)(services);
+            if (parentProcessItem) {
+                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+            }
             dispatch(setBreadcrumbs(breadcrumbs, processItem));
         }
         dispatch(setBreadcrumbs(breadcrumbs));
@@ -70,28 +105,69 @@ export const setCategoryBreadcrumbs = (uuid: string, category: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const ancestors = await services.ancestorsService.ancestors(uuid, '');
         dispatch(updateResources(ancestors));
-        const initialBreadcrumbs: ResourceBreadcrumb[] = [
+        const initialBreadcrumbs: Breadcrumb[] = [
             { label: category, uuid: category }
         ];
         const { collectionPanel: { item } } = getState();
         const path = getState().router.location!.pathname;
         const currentUuid = path.split('/')[2];
         const uuidKind = extractUuidKind(currentUuid);
-        const breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
+        let breadcrumbs = ancestors.reduce((breadcrumbs, ancestor) =>
             ancestor.kind === ResourceKind.GROUP
-                ? [...breadcrumbs, { label: ancestor.name, uuid: ancestor.uuid }]
+                ? [...breadcrumbs, resourceToBreadcrumb(ancestor)]
                 : breadcrumbs,
             initialBreadcrumbs);
         if (uuidKind === ResourceKind.COLLECTION) {
             const collectionItem = item ? item : await services.collectionService.get(currentUuid);
+            const parentProcessItem = await getCollectionParent(collectionItem)(services);
+            if (parentProcessItem) {
+                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+            }
             dispatch(setBreadcrumbs(breadcrumbs, collectionItem));
         } else if (uuidKind === ResourceKind.PROCESS) {
             const processItem = await services.containerRequestService.get(currentUuid);
+            const parentProcessItem = await getProcessParent(processItem)(services);
+            if (parentProcessItem) {
+                breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
+            }
             dispatch(setBreadcrumbs(breadcrumbs, processItem));
         }
         dispatch(setBreadcrumbs(breadcrumbs));
     };
 
+const getProcessParent = (childProcess: ContainerRequestResource) =>
+    async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
+        if (childProcess.requestingContainerUuid) {
+            const parentProcesses = await services.containerRequestService.list({
+                order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
+                filters: new FilterBuilder().addEqual('container_uuid', childProcess.requestingContainerUuid).getFilters(),
+                select: containerRequestFieldsNoMounts,
+            });
+            if (parentProcesses.items.length > 0) {
+                return parentProcesses.items[0];
+            } else {
+                return undefined;
+            }
+        } else {
+            return undefined;
+        }
+    }
+
+const getCollectionParent = (collection: CollectionResource) =>
+    async (services: ServiceRepository): Promise<ContainerRequestResource | undefined> => {
+        const parentProcesses = await services.containerRequestService.list({
+            order: new OrderBuilder<ProcessResource>().addAsc('createdAt').getOrder(),
+            filters: new FilterBuilder().addEqual('output_uuid', collection.uuid).getFilters(),
+            select: containerRequestFieldsNoMounts,
+        });
+        if (parentProcesses.items.length > 0) {
+            return parentProcesses.items[0];
+        } else {
+            return undefined;
+        }
+    }
+
+
 export const setProjectBreadcrumbs = (uuid: string) =>
     (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         const ancestors = getSidePanelTreeNodeAncestorsIds(uuid)(getState().treePicker);
@@ -121,7 +197,7 @@ export const setGroupDetailsBreadcrumbs = (groupUuid: string) =>
 
         const group = getResource<GroupResource>(groupUuid)(getState().resources);
 
-        const breadcrumbs: ResourceBreadcrumb[] = [
+        const breadcrumbs: Breadcrumb[] = [
             { label: SidePanelTreeCategory.GROUPS, uuid: SidePanelTreeCategory.GROUPS },
             { label: group ? group.name : (await services.groupsService.get(groupUuid)).name, uuid: groupUuid },
         ];
@@ -140,13 +216,13 @@ export const setUserProfileBreadcrumbs = (userUuid: string) =>
         try {
             const user = getResource<UserResource>(userUuid)(getState().resources)
                         || await services.userService.get(userUuid, false);
-            const breadcrumbs: ResourceBreadcrumb[] = [
+            const breadcrumbs: Breadcrumb[] = [
                 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
                 { label: user ? user.username : userUuid, uuid: userUuid },
             ];
             dispatch(setBreadcrumbs(breadcrumbs));
         } catch (e) {
-            const breadcrumbs: ResourceBreadcrumb[] = [
+            const breadcrumbs: Breadcrumb[] = [
                 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
                 { label: userUuid, uuid: userUuid },
             ];
index c4134aed4d948255fc0c07e8fb1ab9747e971836..0334097d2eee07ce079b2f7c9863e1bb59a2756d 100644 (file)
@@ -8,25 +8,24 @@ import { RootState } from 'store/store';
 import { Dispatch } from 'redux';
 import { navigateTo } from 'store/navigation/navigation-action';
 import { getProperty } from '../../store/properties/properties';
-import { ResourceBreadcrumb, BREADCRUMBS } from '../../store/breadcrumbs/breadcrumbs-actions';
+import { BREADCRUMBS } from '../../store/breadcrumbs/breadcrumbs-actions';
 import { openSidePanelContextMenu } from 'store/context-menu/context-menu-actions';
-import { ProjectResource } from "models/project";
 
 type BreadcrumbsDataProps = Pick<BreadcrumbsProps, 'items' | 'resources'>;
 type BreadcrumbsActionProps = Pick<BreadcrumbsProps, 'onClick' | 'onContextMenu'>;
 
 const mapStateToProps = () => ({ properties, resources }: RootState): BreadcrumbsDataProps => ({
-    items: (getProperty<ResourceBreadcrumb[]>(BREADCRUMBS)(properties) || []),
+    items: (getProperty<Breadcrumb[]>(BREADCRUMBS)(properties) || []),
     resources,
 });
 
 const mapDispatchToProps = (dispatch: Dispatch): BreadcrumbsActionProps => ({
-    onClick: ({ uuid }: Breadcrumb & ProjectResource) => {
+    onClick: ({ uuid }: Breadcrumb) => {
         dispatch<any>(navigateTo(uuid));
     },
-    onContextMenu: (event, breadcrumb: Breadcrumb & ProjectResource) => {
+    onContextMenu: (event, breadcrumb: Breadcrumb) => {
         dispatch<any>(openSidePanelContextMenu(event, breadcrumb.uuid));
     }
 });
 
-export const Breadcrumbs = connect(mapStateToProps(), mapDispatchToProps)(BreadcrumbsComponent);
\ No newline at end of file
+export const Breadcrumbs = connect(mapStateToProps(), mapDispatchToProps)(BreadcrumbsComponent);