21224: set up user card toolbar Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox...
authorLisa Knox <lisaknox83@gmail.com>
Tue, 26 Mar 2024 15:30:09 +0000 (11:30 -0400)
committerLisa Knox <lisaknox83@gmail.com>
Tue, 26 Mar 2024 15:30:09 +0000 (11:30 -0400)
services/workbench2/src/components/multiselect-toolbar/MultiselectToolbar.tsx
services/workbench2/src/components/multiselect-toolbar/ms-kind-action-differentiator.ts
services/workbench2/src/components/multiselect-toolbar/ms-toolbar-action-filters.ts
services/workbench2/src/views-components/multiselect-toolbar/ms-menu-actions.ts
services/workbench2/src/views-components/multiselect-toolbar/ms-user-details-action-set.ts [new file with mode: 0644]
services/workbench2/src/views-components/project-details-card/project-details-card.tsx

index a7364da26b6821295555567abafc392649ce61f7..69c9dfc5efca250944b9fdfcdd645f63e51dbdf0 100644 (file)
@@ -92,12 +92,24 @@ type IconProps = {
     publicFavorites: PublicFavoritesState;
 }
 
+const disallowedPaths = [
+    "/favorites",
+    "/public-favorites",
+    "/trash",
+    "/group",
+]
+
+const isPathDisallowed = (location: string): boolean => {
+    return disallowedPaths.some(path => location.includes(path))
+}
+
 export const MultiselectToolbar = connect(
     mapStateToProps,
     mapDispatchToProps
 )(
     withStyles(styles)((props: MultiselectToolbarProps & WithStyles<CssRules>) => {
-        const { classes, checkedList, iconProps, user, disabledButtons, selectedResourceUuid, location, isSubPanel } = props;
+        const { classes, checkedList, iconProps, user, disabledButtons, location, isSubPanel } = props;
+        const selectedResourceUuid = isPathDisallowed(location) ? null : props.selectedResourceUuid;
         const singleResourceKind = selectedResourceUuid && !isSubPanel ? [resourceToMsResourceKind(selectedResourceUuid, iconProps.resources, user)] : null
         const currentResourceKinds = singleResourceKind ? singleResourceKind : Array.from(selectedToKindSet(checkedList));
         const currentPathIsTrash = location.includes("/trash");
@@ -126,6 +138,8 @@ export const MultiselectToolbar = connect(
                         selectedResourceUuid === null ? action.isForMulti : true
                     );
 
+        const targetResources = selectedResourceUuid ? {[selectedResourceUuid]: true} as TCheckedList : checkedList
+
         return (
             <React.Fragment>
                 <Toolbar
@@ -149,7 +163,7 @@ export const MultiselectToolbar = connect(
                                         <IconButton
                                             data-cy='multiselect-button'
                                             disabled={disabledButtons.has(name)}
-                                            onClick={() => props.executeMulti(action, checkedList, iconProps.resources)}
+                                            onClick={() => props.executeMulti(action, targetResources, iconProps.resources)}
                                             className={classes.icon}
                                         >
                                             {currentPathIsTrash || (useAlts && useAlts(selectedResourceUuid, iconProps)) ? altIcon && altIcon({}) : icon({})}
@@ -168,7 +182,7 @@ export const MultiselectToolbar = connect(
                                         <IconButton
                                             data-cy='multiselect-button'
                                             onClick={() => {
-                                                props.executeMulti(action, checkedList, iconProps.resources)}}
+                                                props.executeMulti(action, targetResources, iconProps.resources)}}
                                             className={classes.icon}
                                         >
                                             {action.icon({})}
@@ -227,7 +241,7 @@ const resourceToMsResourceKind = (uuid: string, resources: ResourcesState, user:
     const { isAdmin } = user;
     const kind = extractUuidKind(uuid);
 
-    const isFrozen = resourceIsFrozen(resource, resources);
+    const isFrozen = resource?.kind && resource.kind === ResourceKind.PROJECT ? resourceIsFrozen(resource, resources) : false;
     const isEditable = (user.isAdmin || (resource || ({} as EditableResource)).isEditable) && !readonly && !isFrozen;
 
     switch (kind) {
index 5a84d4c573f711a46a4a4665b9acbcff5bf2f18f..1e49ae664fe9d8b6ae7427f12827a5edff1fccb1 100644 (file)
@@ -8,16 +8,18 @@ import { msCollectionActionSet } from "views-components/multiselect-toolbar/ms-c
 import { msProjectActionSet } from "views-components/multiselect-toolbar/ms-project-action-set";
 import { msProcessActionSet } from "views-components/multiselect-toolbar/ms-process-action-set";
 import { msWorkflowActionSet } from "views-components/multiselect-toolbar/ms-workflow-action-set";
+import { UserDetailsActionSet } from "views-components/multiselect-toolbar/ms-user-details-action-set";
 
 export function findActionByName(name: string, actionSet: MultiSelectMenuActionSet) {
     return actionSet[0].find(action => action.name === name);
 }
 
-const { COLLECTION, PROCESS, PROJECT, WORKFLOW } = ResourceKind;
+const { COLLECTION, PROCESS, PROJECT, WORKFLOW, USER } = ResourceKind;
 
 export const kindToActionSet: Record<string, MultiSelectMenuActionSet> = {
     [COLLECTION]: msCollectionActionSet,
     [PROCESS]: msProcessActionSet,
     [PROJECT]: msProjectActionSet,
     [WORKFLOW]: msWorkflowActionSet,
+    [USER]: UserDetailsActionSet,
 };
index 9faba269363c54d9d49c6b5fbd269bd5feb1c8bb..ddd2a1d83b44944c044457107cb857879b344642 100644 (file)
@@ -15,6 +15,7 @@ import {
 } from 'views-components/multiselect-toolbar/ms-project-action-set';
 import { msProcessActionSet, msCommonProcessActionFilter, msAdminProcessActionFilter, msRunningProcessActionFilter } from 'views-components/multiselect-toolbar/ms-process-action-set';
 import { msWorkflowActionSet, msWorkflowActionFilter, msReadOnlyWorkflowActionFilter } from 'views-components/multiselect-toolbar/ms-workflow-action-set';
+import { UserDetailsActionSet } from 'views-components/multiselect-toolbar/ms-user-details-action-set';
 import { ResourceKind } from 'models/resource';
 
 export enum msMenuResourceKind {
@@ -75,7 +76,9 @@ const {
     RUNNING_PROCESS_ADMIN,
     PROCESS_ADMIN,
     PROJECT,
+    ROOT_PROJECT,
     PROJECT_ADMIN,
+    ROOT_PROJECT_ADMIN,
     FROZEN_PROJECT,
     FROZEN_PROJECT_ADMIN,
     READONLY_PROJECT,
@@ -113,4 +116,7 @@ export const multiselectActionsFilters: TMultiselectActionsFilters = {
     
     [WORKFLOW]: [msWorkflowActionSet, msWorkflowActionFilter],
     [READONLY_WORKFLOW]: [msWorkflowActionSet, msReadOnlyWorkflowActionFilter],
+
+    [ROOT_PROJECT]: [UserDetailsActionSet as MultiSelectMenuActionSet, allActionNames(UserDetailsActionSet as MultiSelectMenuActionSet)],
+    [ROOT_PROJECT_ADMIN]: [UserDetailsActionSet as MultiSelectMenuActionSet, allActionNames(UserDetailsActionSet as MultiSelectMenuActionSet)],
 };
index da00e0be28301b386601fb2fa5879b69ed875f2c..ef5eb8aaf67cbda097477db22dc1618fb4826073 100644 (file)
@@ -48,6 +48,7 @@ export enum MultiSelectMenuActionNames {
     DEACTIVATE_USER = 'Deactivate user', 
     SETUP_USER = 'Setup user',
     LOGIN_AS_USER = 'Login as user',
+    USER_ACCOUNT = 'User Account',
 };
 
 export type MultiSelectMenuAction = {
diff --git a/services/workbench2/src/views-components/multiselect-toolbar/ms-user-details-action-set.ts b/services/workbench2/src/views-components/multiselect-toolbar/ms-user-details-action-set.ts
new file mode 100644 (file)
index 0000000..3369825
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { AdvancedIcon, AttributesIcon, UserPanelIcon } from 'components/icon/icon';
+import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
+import { openUserAttributes } from 'store/users/users-actions';
+import { navigateToUserProfile } from 'store/navigation/navigation-action';
+import { MultiSelectMenuActionSet, MultiSelectMenuActionNames } from './ms-menu-actions';
+
+export const UserDetailsActionSet: MultiSelectMenuActionSet= [
+    [
+        {
+            name: MultiSelectMenuActionNames.ATTRIBUTES,
+            icon: AttributesIcon,
+            hasAlts: false,
+            isForMulti: false,
+            execute: (dispatch, resources) => {
+                dispatch<any>(openUserAttributes(resources[0].uuid));
+            },
+        },
+        {
+            name: MultiSelectMenuActionNames.API_DETAILS,
+            icon: AdvancedIcon,
+            hasAlts: false,
+            isForMulti: false,
+            execute: (dispatch, resources) => {
+                dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
+            },
+        },
+        {
+            name: MultiSelectMenuActionNames.USER_ACCOUNT,
+            icon: UserPanelIcon,
+            hasAlts: false,
+            isForMulti: false,
+            execute: (dispatch, resources) => {
+                dispatch<any>(navigateToUserProfile(resources[0].uuid));
+            },
+        },
+    ],
+];
index aad665682bb9aec5b0393778258392ec75909870..61aa0be519413ba6b5da590ab039a96631f5e0ba 100644 (file)
@@ -14,22 +14,19 @@ import { ResourceKind } from 'models/resource';
 import { UserResource } from 'models/user';
 import { UserResourceAccountStatus } from 'views-components/data-explorer/renderers';
 import { FavoriteStar, PublicFavoriteStar } from 'views-components/favorite-star/favorite-star';
-import { MoreVerticalIcon, FreezeIcon } from 'components/icon/icon';
+import { FreezeIcon } from 'components/icon/icon';
 import { Resource } from 'models/resource';
-import { IconButton } from '@material-ui/core';
 import { ContextMenuResource } from 'store/context-menu/context-menu-actions';
 import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
 import { CollectionResource } from 'models/collection';
 import { ContextMenuKind } from 'views-components/context-menu/context-menu';
 import { Dispatch } from 'redux';
-import classNames from 'classnames';
 import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
 import { ExpandChevronRight } from 'components/expand-chevron-right/expand-chevron-right';
 import { MultiselectToolbar } from 'components/multiselect-toolbar/MultiselectToolbar';
 
 type CssRules =
     | 'root'
-    | 'selected'
     | 'cardHeaderContainer'
     | 'cardHeader'
     | 'descriptionToggle'
@@ -41,7 +38,7 @@ type CssRules =
     | 'namePlate'
     | 'faveIcon'
     | 'frozenIcon'
-    | 'contextMenuSection'
+    | 'accountStatusSection'
     | 'toolbarSection'
     | 'tag'
     | 'description';
@@ -52,10 +49,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         marginBottom: '1rem',
         flex: '0 0 auto',
         padding: 0,
-        borderLeft: '2px solid transparent',
-    },
-    selected: {
-        border: '2pcx solid #ccc',
+        border: '2px solid transparent',
     },
     showMore: {
         cursor: 'pointer',
@@ -69,6 +63,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     userNameContainer: {
         display: 'flex',
+        alignItems: 'center',
     },
     cardHeaderContainer: {
         width: '100%',
@@ -116,11 +111,11 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         height: '1rem',
         color: theme.palette.text.primary,
     },
-    contextMenuSection: {
+    accountStatusSection: {
         display: 'flex',
         flexDirection: 'row',
         alignItems: 'center',
-        paddingTop: '0.25rem',
+        paddingLeft: '1rem',
     },
     toolbarSection: {
         marginTop: '-1rem',
@@ -136,7 +131,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
 });
 
-const mapStateToProps = ({auth, selectedResourceUuid, resources, properties}: RootState) => {
+const mapStateToProps = ({ auth, selectedResourceUuid, resources, properties }: RootState) => {
     const currentResource = getResource(properties.currentRouteUuid)(resources);
     const frozenByUser = currentResource && getResource((currentResource as ProjectResource).frozenByUuid as string)(resources);
     const frozenByFullName = frozenByUser && (frozenByUser as Resource & { fullName: string }).fullName;
@@ -253,44 +248,37 @@ const UserCard: React.FC<UserCardProps> = ({ classes, currentResource, handleCon
 
     return (
         <Card
-            className={classNames(classes.root, isSelected ? classes.selected : '')}
+            className={classes.root}
             onClick={() => handleCardClick(uuid)}
             data-cy='user-details-card'
         >
-            <CardHeader
-                className={classes.cardHeader}
-                title={
-                    <section className={classes.userNameContainer}>
-                        <Typography
-                            noWrap
-                            variant='h6'
-                        >
-                            {fullName}
-                        </Typography>
-                    </section>
-                }
-                action={
-                    <section className={classes.contextMenuSection}>
-                        {!currentResource.isActive && (
-                            <Typography>
-                                <UserResourceAccountStatus uuid={uuid} />
-                            </Typography>
-                        )}
-                        <Tooltip
-                            title='More options'
-                            disableFocusListener
-                        >
-                            <IconButton
-                                aria-label='More options'
-                                data-cy='kebab-icon'
-                                onClick={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
+            <Grid
+                container
+                wrap='nowrap'
+                className={classes.cardHeaderContainer}
+            >
+                <CardHeader
+                    className={classes.cardHeader}
+                    title={
+                        <section className={classes.userNameContainer}>
+                            <Typography
+                                noWrap
+                                variant='h6'
                             >
-                                <MoreVerticalIcon />
-                            </IconButton>
-                        </Tooltip>
-                    </section>
-                }
-            />
+                                {fullName}
+                            </Typography>
+                            <section className={classes.accountStatusSection}>
+                                {!currentResource.isActive && (
+                                    <Typography>
+                                        <UserResourceAccountStatus uuid={uuid} />
+                                    </Typography>
+                            )}
+                            </section>
+                        </section>
+                    }
+                />
+                {isSelected && <MultiselectToolbar />}
+            </Grid>
         </Card>
     );
 };
@@ -310,11 +298,15 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ classes, currentResource, fro
 
     return (
         <Card
-            className={classNames(classes.root, isSelected ? classes.selected : '')}
+            className={classes.root}
             onClick={() => handleCardClick(uuid)}
             data-cy='project-details-card'
         >
-            <Grid container wrap='nowrap' className={classes.cardHeaderContainer}>
+            <Grid
+                container
+                wrap='nowrap'
+                className={classes.cardHeaderContainer}
+            >
                 <CardHeader
                     className={classes.cardHeader}
                     title={