17119: implement review feedback.
authorWard Vandewege <ward@curii.com>
Sun, 21 Mar 2021 02:03:14 +0000 (22:03 -0400)
committerWard Vandewege <ward@curii.com>
Sun, 21 Mar 2021 02:03:14 +0000 (22:03 -0400)
Arvados-DCO-1.1-Signed-off-by: Ward Vandewege <ward@curii.com>

src/components/icon/icon.tsx
src/components/tree/tree.tsx
src/store/context-menu/context-menu-actions.ts
src/store/resources/resources.ts
src/views-components/data-explorer/renderers.tsx
src/views-components/details-panel/project-details.tsx
src/views-components/side-panel-tree/side-panel-tree.tsx

index 9eb60332e7261e0ba75f40bc62334432d988c4ff..6bbacaf4bebf6f368778e968ad275a550e960b19 100644 (file)
@@ -28,6 +28,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
 import FlipToFront from '@material-ui/icons/FlipToFront';
 import Folder from '@material-ui/icons/Folder';
 import FolderShared from '@material-ui/icons/FolderShared';
+import Pageview from '@material-ui/icons/Pageview';
 import GetApp from '@material-ui/icons/GetApp';
 import Help from '@material-ui/icons/Help';
 import HelpOutline from '@material-ui/icons/HelpOutline';
@@ -126,6 +127,7 @@ export const PaginationLeftArrowIcon: IconType = (props) => <ChevronLeft {...pro
 export const PaginationRightArrowIcon: IconType = (props) => <ChevronRight {...props} />;
 export const ProcessIcon: IconType = (props) => <BubbleChart {...props} />;
 export const ProjectIcon: IconType = (props) => <Folder {...props} />;
+export const FilterGroupIcon: IconType = (props) => <Pageview {...props} />;
 export const ProjectsIcon: IconType = (props) => <Inbox {...props} />;
 export const ProvenanceGraphIcon: IconType = (props) => <DeviceHub {...props} />;
 export const RemoveIcon: IconType = (props) => <Delete {...props} />;
index 908ee28ca8b24a139c21ef76358a6cbeaa723c5b..cf4d708daaabab31eddd615baac1b82a298b88a7 100644 (file)
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { List, ListItem, ListItemIcon, Checkbox, Radio, Collapse } from "@material-ui/core";
 import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
-import { CollectionIcon, DefaultIcon, DirectoryIcon, FileIcon, ProjectIcon } from '~/components/icon/icon';
+import { CollectionIcon, DefaultIcon, DirectoryIcon, FileIcon, ProjectIcon, FilterGroupIcon } from '~/components/icon/icon';
 import { ReactElement } from "react";
 import CircularProgress from '@material-ui/core/CircularProgress';
 import classnames from "classnames";
@@ -13,6 +13,7 @@ import classnames from "classnames";
 import { ArvadosTheme } from '~/common/custom-theme';
 import { SidePanelRightArrowIcon } from '../icon/icon';
 import { ResourceKind } from '~/models/resource';
+import { GroupClass } from '~/models/group';
 
 type CssRules = 'list'
     | 'listItem'
@@ -162,31 +163,35 @@ const FLAT_TREE_ACTIONS = {
     toggleActive: 'TOGGLE_ACTIVE',
 };
 
-const ItemIcon = React.memo(({type, kind, active, classes}: any) => {
+const ItemIcon = React.memo(({type, kind, active, groupClass, classes}: any) => {
     let Icon = ProjectIcon;
 
-        if (type) {
-            switch (type) {
-                case 'directory':
-                    Icon = DirectoryIcon;
-                    break;
-                case 'file':
-                    Icon = FileIcon;
-                    break;
-                default:
-                    Icon = DefaultIcon;
-            }
+    if (groupClass === GroupClass.FILTER) {
+        Icon = FilterGroupIcon;
+    }
+
+    if (type) {
+        switch (type) {
+            case 'directory':
+                Icon = DirectoryIcon;
+                break;
+            case 'file':
+                Icon = FileIcon;
+                break;
+            default:
+                Icon = DefaultIcon;
         }
+    }
 
-        if (kind) {
-            switch(kind) {
-                case ResourceKind.COLLECTION:
-                    Icon = CollectionIcon;
-                    break;
-                default:
-                    break;
-            }
+    if (kind) {
+        switch(kind) {
+            case ResourceKind.COLLECTION:
+                Icon = CollectionIcon;
+                break;
+            default:
+                break;
         }
+    }
 
     return <Icon className={classnames({ [classes.active]: active }, classes.childItemIcon)} />;
 });
@@ -228,7 +233,7 @@ const FlatTree = (props: FlatTreeProps) =>
                     </i>
                     <div data-action={FLAT_TREE_ACTIONS.toggleActive} className={props.classes.renderContainer}>
                         <span style={{ display: 'flex', alignItems: 'center' }}>
-                            <ItemIcon type={item.data.type} active={item.active} kind={item.data.kind} classes={props.classes} />
+                            <ItemIcon type={item.data.type} active={item.active} kind={item.data.kind} groupClass={item.data.kind === ResourceKind.GROUP ? item.data.groupClass : ''} classes={props.classes} />
                             <span style={{ fontSize: '0.875rem' }}>
                                 {item.data.name}
                             </span>
index 9a5b30997e2a84355ff854a8943c5a4da0493a03..f8049a5c532b84ba9f092a0c7a7e0549257c153c 100644 (file)
@@ -205,9 +205,6 @@ export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>, pro
     };
 
 export const isProjectRoute = (router: RouterState) => {
-    if (router === undefined) {
-      return false;
-    }
     const pathname = router.location ? router.location.pathname : '';
     const matchProject = matchProjectRoute(pathname);
     return Boolean(matchProject);
@@ -223,24 +220,20 @@ export const resourceUuidToContextMenuKind = (uuid: string) =>
         let inFilterGroup = false;
         const { router } = getState();
         if (isProjectRoute(router)) {
-          const projectUuid = getProjectPanelCurrentUuid(getState());
-          if (projectUuid !== undefined) {
-            const project = getResource<GroupResource>(projectUuid)(getState().resources);
-            if (project) {
-              if (project.groupClass === GroupClass.FILTER) {
-                inFilterGroup = true;
-              }
+            const projectUuid = getProjectPanelCurrentUuid(getState());
+            if (projectUuid !== undefined) {
+                const project = getResource<GroupResource>(projectUuid)(getState().resources);
+                if (project && project.groupClass === GroupClass.FILTER) {
+                    inFilterGroup = true;
+                }
             }
-          }
         }
 
         const isEditable = (isAdminUser || (resource || {} as EditableResource).isEditable) && !inFilterGroup;
         switch (kind) {
             case ResourceKind.PROJECT:
                 return (isAdminUser && !inFilterGroup)
-                    ? (resource && resource.groupClass === GroupClass.PROJECT)
-                        ? ContextMenuKind.PROJECT_ADMIN
-                        : ContextMenuKind.READONLY_PROJECT
+                    ? ContextMenuKind.PROJECT_ADMIN
                     : isEditable
                         ? ContextMenuKind.PROJECT
                         : ContextMenuKind.READONLY_PROJECT;
index 915235d1ec14ef14ec4c97801513e378033f3397..696a136280c1a72fef39a8a204e5fd9557439508 100644 (file)
@@ -6,8 +6,6 @@ import { Resource, EditableResource } from "~/models/resource";
 import { ResourceKind } from '~/models/resource';
 import { ProjectResource } from "~/models/project";
 import { GroupResource } from "~/models/group";
-import { extractUuidObjectType, ResourceObjectType } from "~/models/resource";
-import { GroupClass } from '~/models/group';
 
 export type ResourcesState = { [key: string]: Resource };
 
@@ -38,15 +36,6 @@ export const getResourceWithEditableStatus = <T extends EditableResource & Group
         const resource = JSON.parse(JSON.stringify(state[id] as T));
 
         if (resource) {
-            const objectType = extractUuidObjectType(resource.uuid);
-            switch (objectType) {
-              case ResourceObjectType.GROUP:
-                // filter groups are read-only for now
-                if (resource.groupClass === GroupClass.FILTER) {
-                  resource.isEditable = false;
-                  return resource;
-                }
-            }
             resource.isEditable = userUuid ? getResourceWritableBy(state, id, userUuid).indexOf(userUuid) > -1 : false;
         }
 
index 6cf29faecf444683540de031966273298fc19314..28a6f253138f819dfb39274afe7e7818d43f5edc 100644 (file)
@@ -6,7 +6,7 @@ import * as React from 'react';
 import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } from '@material-ui/core';
 import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
 import { ResourceKind, TrashableResource } from '~/models/resource';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon } from '~/components/icon/icon';
+import { ProjectIcon, FilterGroupIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon } from '~/components/icon/icon';
 import { formatDate, formatFileSize, formatTime } from '~/common/formatters';
 import { resourceLabel } from '~/common/labels';
 import { connect, DispatchProp } from 'react-redux';
@@ -28,6 +28,7 @@ import { withResourceData } from '~/views-components/data-explorer/with-resource
 import { CollectionResource } from '~/models/collection';
 import { IllegalNamingWarning } from '~/components/warning/warning';
 import { loadResource } from '~/store/resources/resources-actions';
+import { GroupClass } from '~/models/group';
 
 const renderName = (dispatch: Dispatch, item: GroupContentsResource) =>
     <Grid container alignItems="center" wrap="nowrap" spacing={16}>
@@ -59,6 +60,9 @@ export const ResourceName = connect(
 const renderIcon = (item: GroupContentsResource) => {
     switch (item.kind) {
         case ResourceKind.PROJECT:
+            if (item.groupClass === GroupClass.FILTER) {
+                return <FilterGroupIcon />;
+            }
             return <ProjectIcon />;
         case ResourceKind.COLLECTION:
             if (item.uuid === item.currentVersionUuid) {
index 4a615506ea35f0cf717f9fdbc22248735833c085..3c2bc0995f9cc1567dd34af7c7e5bcc5490130c0 100644 (file)
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { connect } from 'react-redux';
 import { openProjectPropertiesDialog } from '~/store/details-panel/details-panel-action';
-import { ProjectIcon, RenameIcon } from '~/components/icon/icon';
+import { ProjectIcon, RenameIcon, FilterGroupIcon } from '~/components/icon/icon';
 import { ProjectResource } from '~/models/project';
 import { formatDate } from '~/common/formatters';
 import { ResourceKind } from '~/models/resource';
@@ -18,9 +18,13 @@ import { ArvadosTheme } from '~/common/custom-theme';
 import { Dispatch } from 'redux';
 import { getPropertyChip } from '../resource-properties-form/property-chip';
 import { ResourceOwnerWithName } from '../data-explorer/renderers';
+import { GroupClass } from "~/models/group";
 
 export class ProjectDetails extends DetailsData<ProjectResource> {
     getIcon(className?: string) {
+        if (this.item.groupClass === GroupClass.FILTER) {
+            return <FilterGroupIcon className={className} />;
+        }
         return <ProjectIcon className={className} />;
     }
 
@@ -59,8 +63,7 @@ type ProjectDetailsComponentProps = ProjectDetailsComponentDataProps & ProjectDe
 const ProjectDetailsComponent = connect(null, mapDispatchToProps)(
     withStyles(styles)(
         ({ classes, project, onClick }: ProjectDetailsComponentProps) => <div>
-            <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROJECT)} />
-            <DetailsAttribute label='Group class' value={project.groupClass} />
+            <DetailsAttribute label='Type' value={project.groupClass === GroupClass.FILTER ? 'Filter group' : resourceLabel(ResourceKind.PROJECT)} />
             <DetailsAttribute label='Owner' linkToUuid={project.ownerUuid}
                 uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
             <DetailsAttribute label='Last modified' value={formatDate(project.modifiedAt)} />
@@ -76,9 +79,12 @@ const ProjectDetailsComponent = connect(null, mapDispatchToProps)(
                 }
             </DetailsAttribute>
             <DetailsAttribute label='Properties'>
-                <div onClick={onClick}>
-                    <RenameIcon className={classes.editIcon} />
-                </div>
+                {project.groupClass !== GroupClass.FILTER ?
+                    <div onClick={onClick}>
+                        <RenameIcon className={classes.editIcon} />
+                    </div>
+                    : ''
+                }
             </DetailsAttribute>
             {
                 Object.keys(project.properties).map(k =>
index e0d9777da006aa317f9684eba4492ffd5934ff48..4c6f01a146c910885de60244924529aaed575f59 100644 (file)
@@ -9,13 +9,15 @@ import { TreePicker, TreePickerProps } from "../tree-picker/tree-picker";
 import { TreeItem } from "~/components/tree/tree";
 import { ProjectResource } from "~/models/project";
 import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon";
-import { ProcessIcon, ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, TrashIcon, PublicFavoriteIcon } from '~/components/icon/icon';
+import { ProcessIcon, ProjectIcon, FilterGroupIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, TrashIcon, PublicFavoriteIcon } from '~/components/icon/icon';
 import { WorkflowIcon } from '~/components/icon/icon';
 import { activateSidePanelTreeItem, toggleSidePanelTreeItemCollapse, SIDE_PANEL_TREE, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
 import { openSidePanelContextMenu } from '~/store/context-menu/context-menu-actions';
 import { noop } from 'lodash';
 import { ResourceKind } from "~/models/resource";
 import { IllegalNamingWarning } from "~/components/warning/warning";
+import { GroupClass } from "~/models/group";
+
 export interface SidePanelTreeProps {
     onItemActivation: (id: string) => void;
     sidePanelProgress?: boolean;
@@ -58,7 +60,9 @@ const renderSidePanelItem = (item: TreeItem<ProjectResource>) => {
 const getProjectPickerIcon = (item: TreeItem<ProjectResource | string>) =>
     typeof item.data === 'string'
         ? getSidePanelIcon(item.data)
-        : ProjectIcon;
+        : (item.data && item.data.groupClass === GroupClass.FILTER)
+            ? FilterGroupIcon
+            : ProjectIcon;
 
 const getSidePanelIcon = (category: string) => {
     switch (category) {