From ed59f8b637bc1131ec95e7215efb8bfa4fde9f04 Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Thu, 24 Mar 2022 23:55:24 -0400 Subject: [PATCH] 18559: Add uuid with copy and action menu to user profile panel Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- .../context-menu/context-menu-actions.ts | 11 +++ src/views/user-panel/user-panel.tsx | 15 +--- .../user-profile-panel-root.tsx | 75 ++++++++++++++++--- .../user-profile-panel/user-profile-panel.tsx | 13 ++-- 4 files changed, 87 insertions(+), 27 deletions(-) diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 38433eb2..fb5da9fc 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -208,6 +208,17 @@ export const openPermissionEditContextMenu = (event: React.MouseEvent, user: UserResource) => + (dispatch: Dispatch, getState: () => RootState) => { + dispatch(openContextMenu(event, { + name: '', + uuid: user.uuid, + ownerUuid: user.ownerUuid, + kind: user.kind, + menuKind: ContextMenuKind.USER + })); + }; + export const resourceUuidToContextMenuKind = (uuid: string, readonly = false) => (dispatch: Dispatch, getState: () => RootState) => { const { isAdmin: isAdminUser, uuid: userUuid } = getState().auth.user!; diff --git a/src/views/user-panel/user-panel.tsx b/src/views/user-panel/user-panel.tsx index 3c835673..169b32ab 100644 --- a/src/views/user-panel/user-panel.tsx +++ b/src/views/user-panel/user-panel.tsx @@ -9,7 +9,7 @@ import { connect, DispatchProp } from 'react-redux'; import { DataColumns } from 'components/data-table/data-table'; import { RootState } from 'store/store'; import { SortDirection } from 'components/data-table/data-column'; -import { openContextMenu } from "store/context-menu/context-menu-actions"; +import { openUserContextMenu } from "store/context-menu/context-menu-actions"; import { getResource, ResourcesState } from "store/resources/resources"; import { UserResourceFullName, @@ -20,7 +20,6 @@ import { ResourceUsername } from "views-components/data-explorer/renderers"; import { navigateToUserProfile } from "store/navigation/navigation-action"; -import { ContextMenuKind } from "views-components/context-menu/context-menu"; import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view'; import { createTree } from 'models/tree'; import { compose, Dispatch } from 'redux'; @@ -109,7 +108,7 @@ interface UserPanelDataProps { interface UserPanelActionProps { openUserCreateDialog: () => void; handleRowClick: (uuid: string) => void; - onContextMenu: (event: React.MouseEvent, item: any) => void; + handleContextMenu: (event, resource: UserResource) => void; } const mapStateToProps = (state: RootState) => { @@ -121,7 +120,7 @@ const mapStateToProps = (state: RootState) => { const mapDispatchToProps = (dispatch: Dispatch) => ({ openUserCreateDialog: () => dispatch(openUserCreateDialog()), handleRowClick: (uuid: string) => dispatch(navigateToUserProfile(uuid)), - onContextMenu: (event: React.MouseEvent, item: any) => dispatch(openContextMenu(event, item)) + handleContextMenu: (event, resource: UserResource) => dispatch(openUserContextMenu(event, resource)), }); type UserPanelProps = UserPanelDataProps & UserPanelActionProps & DispatchProp & WithStyles; @@ -161,13 +160,7 @@ export const UserPanel = compose( event.stopPropagation(); const resource = getResource(resourceUuid)(this.props.resources); if (resource) { - this.props.onContextMenu(event, { - name: '', - uuid: resource.uuid, - ownerUuid: resource.ownerUuid, - kind: resource.kind, - menuKind: ContextMenuKind.USER - }); + this.props.handleContextMenu(event, resource); } } } diff --git a/src/views/user-profile-panel/user-profile-panel-root.tsx b/src/views/user-profile-panel/user-profile-panel-root.tsx index febe0ab9..4fab7efd 100644 --- a/src/views/user-profile-panel/user-profile-panel-root.tsx +++ b/src/views/user-profile-panel/user-profile-panel-root.tsx @@ -4,6 +4,8 @@ import React from 'react'; import { Field, InjectedFormProps } from "redux-form"; +import { DispatchProp } from 'react-redux'; +import { UserResource } from 'models/user'; import { TextField } from "components/text-field/text-field"; import { DataExplorer } from "views-components/data-explorer/data-explorer"; import { NativeSelectField } from "components/select-field/select-field"; @@ -18,20 +20,24 @@ import { Grid, InputLabel, Tabs, Tab, - Paper + Paper, + Tooltip, + IconButton, } from '@material-ui/core'; import { ArvadosTheme } from 'common/custom-theme'; -import { User } from "models/user"; import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view'; import { PROFILE_EMAIL_VALIDATION } from "validators/validators"; import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions'; import { noop } from 'lodash'; -import { GroupsIcon } from 'components/icon/icon'; +import { CopyIcon, GroupsIcon, MoreOptionsIcon } from 'components/icon/icon'; import { DataColumns } from 'components/data-table/data-table'; import { ResourceLinkHeadUuid, ResourceLinkHeadPermissionLevel, ResourceLinkHead, ResourceLinkDelete, ResourceLinkTailIsVisible } from 'views-components/data-explorer/renderers'; import { createTree } from 'models/tree'; +import { getResource, ResourcesState } from 'store/resources/resources'; +import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions'; +import CopyToClipboard from 'react-copy-to-clipboard'; -type CssRules = 'root' | 'adminRoot' | 'gridItem' | 'label' | 'readOnlyValue' | 'title' | 'description' | 'actions' | 'content'; +type CssRules = 'root' | 'adminRoot' | 'gridItem' | 'label' | 'readOnlyValue' | 'title' | 'description' | 'actions' | 'content' | 'copyIcon'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { @@ -65,6 +71,15 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ content: { // reserve space for the tab bar height: `calc(100% - ${theme.spacing.unit * 7}px)`, + }, + copyIcon: { + marginLeft: theme.spacing.unit, + color: theme.palette.grey["500"], + cursor: 'pointer', + display: 'inline', + '& svg': { + fontSize: '1rem' + } } }); @@ -72,6 +87,7 @@ export interface UserProfilePanelRootActionProps { openSetupDialog: (uuid: string) => void; loginAs: (uuid: string) => void; openDeactivateDialog: (uuid: string) => void; + handleContextMenu: (event, resource: UserResource) => void; } export interface UserProfilePanelRootDataProps { @@ -79,7 +95,8 @@ export interface UserProfilePanelRootDataProps { isSelf: boolean; isPristine: boolean; isValid: boolean; - initialValues?: User; + userUuid: string; + resources: ResourcesState localCluster: string; } @@ -94,7 +111,7 @@ const RoleTypes = [ { key: 'Other', value: 'Other' } ]; -type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & WithStyles; +type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & DispatchProp & WithStyles; export enum UserProfileGroupsColumnNames { NAME = "Name", @@ -172,6 +189,14 @@ export const UserProfilePanelRoot = withStyles(styles)( this.setState({ value: TABS.PROFILE}); } + onCopy = (message: string) => { + this.props.dispatch(snackbarActions.OPEN_SNACKBAR({ + message, + hideDuration: 2000, + kind: SnackbarKind.SUCCESS + })); + } + render() { return @@ -181,6 +206,30 @@ export const UserProfilePanelRoot = withStyles(styles)( {this.state.value === TABS.PROFILE && + + + + {this.props.userUuid} + + + this.onCopy!("Copied")}> + + + + + + + + + this.handleContextMenu(event, this.props.userUuid)}> + + + + +
@@ -305,7 +354,7 @@ export const UserProfilePanelRoot = withStyles(styles)( @@ -330,7 +379,7 @@ export const UserProfilePanelRoot = withStyles(styles)( @@ -355,7 +404,7 @@ export const UserProfilePanelRoot = withStyles(styles)( @@ -371,5 +420,13 @@ export const UserProfilePanelRoot = withStyles(styles)( this.setState({ value }); } + handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { + event.stopPropagation(); + const resource = getResource(resourceUuid)(this.props.resources); + if (resource) { + this.props.handleContextMenu(event, resource); + } + } + } ); diff --git a/src/views/user-profile-panel/user-profile-panel.tsx b/src/views/user-profile-panel/user-profile-panel.tsx index e23b8bce..7a55faf3 100644 --- a/src/views/user-profile-panel/user-profile-panel.tsx +++ b/src/views/user-profile-panel/user-profile-panel.tsx @@ -6,35 +6,34 @@ import { RootState } from 'store/store'; import { compose, Dispatch } from 'redux'; import { reduxForm, isPristine, isValid } from 'redux-form'; import { connect } from 'react-redux'; +import { UserResource } from 'models/user'; import { saveEditedUser } from 'store/user-profile/user-profile-actions'; import { UserProfilePanelRoot, UserProfilePanelRootDataProps } from 'views/user-profile-panel/user-profile-panel-root'; import { openSetupDialog, openDeactivateDialog, USER_PROFILE_FORM } from "store/user-profile/user-profile-actions"; import { matchUserProfileRoute } from 'routes/routes'; -import { UserResource } from 'models/user'; -import { getResource } from 'store/resources/resources'; import { loginAs } from 'store/users/users-actions'; +import { openUserContextMenu } from 'store/context-menu/context-menu-actions'; const mapStateToProps = (state: RootState): UserProfilePanelRootDataProps => { const pathname = state.router.location ? state.router.location.pathname : ''; const match = matchUserProfileRoute(pathname); const uuid = match ? match.params.id : state.auth.user?.uuid || ''; - // get user resource - const user = getResource(uuid)(state.resources); - // const subprocesses = getSubprocesses(uuid)(resources); return { isAdmin: state.auth.user!.isAdmin, isSelf: state.auth.user!.uuid === uuid, isPristine: isPristine(USER_PROFILE_FORM)(state), isValid: isValid(USER_PROFILE_FORM)(state), - initialValues: user, - localCluster: state.auth.localCluster + localCluster: state.auth.localCluster, + userUuid: uuid, + resources: state.resources, }}; const mapDispatchToProps = (dispatch: Dispatch) => ({ openSetupDialog: (uuid: string) => dispatch(openSetupDialog(uuid)), loginAs: (uuid: string) => dispatch(loginAs(uuid)), openDeactivateDialog: (uuid: string) => dispatch(openDeactivateDialog(uuid)), + handleContextMenu: (event, resource: UserResource) => dispatch(openUserContextMenu(event, resource)), }); export const UserProfilePanel = compose( -- 2.30.2