Skip to content

Commit

Permalink
Implementing effective roles and their switching for superadmins. Red…
Browse files Browse the repository at this point in the history
…esigning logged in user badge whilst at it.
  • Loading branch information
Martin Krulis committed Sep 18, 2019
1 parent 0da7e86 commit 1474a04
Show file tree
Hide file tree
Showing 21 changed files with 269 additions and 124 deletions.
14 changes: 5 additions & 9 deletions src/components/SystemMessages/MessagesList/MessagesList.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,11 @@ class MessagesList extends Component {
className: 'valign-middle',
}),

new SortableTableColumnDescriptor(
'role',
<FormattedMessage id="app.systemMessagesList.role" defaultMessage="Role" />,
{
comparator: ({ role: r1 }, { role: r2 }) => Number(rolePriorities[r2]) - Number(rolePriorities[r1]),
cellRenderer: role => role && roleLabels[role],
className: 'valign-middle',
}
),
new SortableTableColumnDescriptor('role', <FormattedMessage id="generic.role" defaultMessage="Role" />, {
comparator: ({ role: r1 }, { role: r2 }) => Number(rolePriorities[r2]) - Number(rolePriorities[r1]),
cellRenderer: role => role && roleLabels[role],
className: 'valign-middle',
}),

new SortableTableColumnDescriptor(
'type',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Table } from 'react-bootstrap';
import classnames from 'classnames';

import { knownRoles, roleLabels, UserRoleIcon } from '../../helpers/usersRoles';

const EffectiveRoleSwitching = ({ effectiveRole, setEffectiveRole, updating = null }) => (
<Table hover className="no-margin">
<tbody>
{knownRoles.map(role => (
<tr
key={role}
className={classnames({
'bg-info': role === (updating || effectiveRole),
})}
onClick={ev => {
ev.preventDefault();
setEffectiveRole(role);
}}>
<td className="shrink-col">
<input
type="radio"
name="effectiveRole"
value={role}
checked={role === (updating || effectiveRole)}
disabled={Boolean(updating)}
readOnly
/>
</td>
<td className="shrink-col">
<UserRoleIcon role={role}></UserRoleIcon>
</td>
<td>{roleLabels[role]}</td>
</tr>
))}
</tbody>
</Table>
);

EffectiveRoleSwitching.propTypes = {
effectiveRole: PropTypes.string,
updating: PropTypes.string,
setEffectiveRole: PropTypes.func.isRequired,
};

export default EffectiveRoleSwitching;
2 changes: 2 additions & 0 deletions src/components/Users/EffectiveRoleSwitching/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import EffectiveRoleSwitching from './EffectiveRoleSwitching';
export default EffectiveRoleSwitching;
7 changes: 5 additions & 2 deletions src/components/Users/UsersName/UsersName.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ const UsersName = ({
fullName,
avatarUrl,
name: { firstName },
size = 20,
size = null,
large = false,
isVerified,
noLink,
noLink = false,
privateData = null,
showEmail = null,
showExternalIdentifiers = false,
links: { USER_URI_FACTORY },
currentUserId,
}) => {
if (size === null) {
size = large ? 45 : 20;
}
const email = privateData && privateData.email && showEmail && encodeURIComponent(privateData.email);
const externalIds = privateData && privateData.externalIds;
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const EditSystemMessageForm = ({
isOpen,
onClose,
createNew = false,
intl: { locale, formatMessage },
intl: { formatMessage },
}) => (
<Modal show={isOpen} backdrop="static" size="lg" onHide={onClose}>
<Modal.Header closeButton>
Expand Down
13 changes: 7 additions & 6 deletions src/components/layout/Sidebar/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import { getLocalizedResourceName } from '../../../helpers/localizedData';
import { isSupervisorRole, isEmpoweredSupervisorRole, isSuperadminRole } from '../../helpers/usersRoles';
import withLinks from '../../../helpers/withLinks';
import { getExternalIdForCAS } from '../../../helpers/cas';
import { safeGet, EMPTY_OBJ } from '../../../helpers/common';
import { EMPTY_OBJ } from '../../../helpers/common';

import styles from './sidebar.less';

const getUserData = defaultMemoize(user => getJsData(user));

const Sidebar = ({
loggedInUser,
effectiveRole = null,
studentOf,
supervisorOf,
currentUrl,
Expand All @@ -45,7 +46,6 @@ const Sidebar = ({
intl: { locale },
}) => {
const user = getUserData(loggedInUser);
const role = safeGet(user, ['privateData', 'role']);
const createLink = item => GROUP_DETAIL_URI_FACTORY(getId(item));
const studentOfItems =
studentOf &&
Expand Down Expand Up @@ -124,7 +124,7 @@ const Sidebar = ({
/>
)}

{isSupervisorRole(role) && supervisorOfItems && (
{isSupervisorRole(effectiveRole) && supervisorOfItems && (
<MenuGroup
title={
<FormattedMessage id="app.sidebar.menu.supervisorOfGroups" defaultMessage="Supervisor of Groups" />
Expand All @@ -139,7 +139,7 @@ const Sidebar = ({
/>
)}

{isSupervisorRole(role) && (
{isSupervisorRole(effectiveRole) && (
<MenuItem
title={<FormattedMessage id="app.sidebar.menu.exercises" defaultMessage="Exercises" />}
icon="puzzle-piece"
Expand All @@ -148,7 +148,7 @@ const Sidebar = ({
/>
)}

{isEmpoweredSupervisorRole(role) && (
{isEmpoweredSupervisorRole(effectiveRole) && (
<MenuItem
title={<FormattedMessage id="app.sidebar.menu.pipelines" defaultMessage="Pipelines" />}
icon="random"
Expand Down Expand Up @@ -183,7 +183,7 @@ const Sidebar = ({
</React.Fragment>
)}

{isSuperadminRole(role) && <Admin currentUrl={currentUrl} />}
{isSuperadminRole(effectiveRole) && <Admin currentUrl={currentUrl} />}
</section>
</aside>
);
Expand All @@ -195,6 +195,7 @@ Sidebar.propTypes = {
search: PropTypes.string.isRequired,
}).isRequired,
loggedInUser: ImmutablePropTypes.map,
effectiveRole: PropTypes.string,
studentOf: ImmutablePropTypes.map,
supervisorOf: ImmutablePropTypes.map,
currentUrl: PropTypes.string,
Expand Down
12 changes: 6 additions & 6 deletions src/components/widgets/Avatar/FakeAvatar.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';

const getSize = (size, small) => (small ? size * (2 / 3) : size);
const getSize = (size, borderWidth, small) => (small ? size * (2 / 3) : size) - 2 * borderWidth;

const FakeAvatar = ({ size = 45, borderWidth = 2, light = false, children, small = false, altClassName = '' }) => (
<span
Expand All @@ -10,14 +10,14 @@ const FakeAvatar = ({ size = 45, borderWidth = 2, light = false, children, small
background: !light ? '#286090' : 'white',
color: !light ? 'white' : 'gray',
textAlign: 'center',
width: getSize(size, small),
height: getSize(size, small),
lineHeight: `${getSize(size, small) - 2 * borderWidth}px`,
width: getSize(size, borderWidth, small),
height: getSize(size, borderWidth, small),
lineHeight: `${getSize(size, borderWidth, small) - 2 * borderWidth}px`,
borderStyle: 'solid',
borderWidth,
borderColor: !light ? 'transparent' : 'gray',
borderRadius: Math.ceil(getSize(size, small) / 2),
fontSize: Math.floor(Math.max(14, getSize(size, small) / 2)),
borderRadius: Math.floor(getSize(size, borderWidth, small) / 2),
fontSize: Math.floor(Math.max(10, getSize(size, borderWidth, small) / 2)),
fontWeight: 'bolder',
}}
className={altClassName}>
Expand Down
158 changes: 111 additions & 47 deletions src/components/widgets/Badge/Badge.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,137 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, FormattedRelative } from 'react-intl';
import { Link } from 'react-router-dom';
import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import { Tooltip, OverlayTrigger, Modal } from 'react-bootstrap';

import UserName from '../../Users/UsersName';
import EffectiveRoleSwitching from '../../Users/EffectiveRoleSwitching';
import withLinks from '../../../helpers/withLinks';
import Icon from '../../icons';
import AvatarContainer from '../../../containers/AvatarContainer/AvatarContainer';
import { isSuperadminRole } from '../../helpers/usersRoles';

class Badge extends Component {
state = { effectiveRoleDialogOpened: false, effectiveRoleUpdating: null };

openEffectiveRoleDialog = () => {
this.setState({ effectiveRoleDialogOpened: true, effectiveRoleUpdating: null });
};

closeEffectiveRoleDialog = () => {
this.setState({ effectiveRoleDialogOpened: false, effectiveRoleUpdating: null });
};

setEffectiveRole = role => {
const { setEffectiveRole } = this.props;
this.setState({ effectiveRoleUpdating: role });
setEffectiveRole(role).then(() => {
this.closeEffectiveRoleDialog();
window.location.reload();
});
};

render() {
const {
id,
fullName,
name: { firstName },
avatarUrl,
user,
effectiveRole,
expiration,
logout,
size = 45,
links: { USER_URI_FACTORY, EDIT_USER_URI_FACTORY },
small = false,
links: { EDIT_USER_URI_FACTORY },
} = this.props;

return (
<div className="user-panel">
<div className="pull-left image">
<AvatarContainer avatarUrl={avatarUrl} fullName={fullName} firstName={firstName} size={size} />
</div>
<div className="info">
<p>
<Link to={USER_URI_FACTORY(id)}>{fullName}</Link>
</p>
<Link to={EDIT_USER_URI_FACTORY(id)}>
<Icon icon="edit" gapRight />
<FormattedMessage id="generic.settings" defaultMessage="Settings" />
</Link>
&nbsp;
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tokenExpiration">
<FormattedMessage id="app.badge.sessionExpiration" defaultMessage="Session expiration:" />{' '}
<FormattedRelative value={expiration} />
</Tooltip>
}>
<a
href="#"
onClick={e => {
e.preventDefault();
logout();
}}>
<Icon icon="sign-out-alt" className="text-danger" />
&nbsp;
<FormattedMessage id="app.logout" defaultMessage="Logout" />
</a>
</OverlayTrigger>
<React.Fragment>
<div className="user-panel">
<div className="text-center">
{small ? (
<AvatarContainer
avatarUrl={user.avatarUrl}
fullName={user.fullName}
firstName={user.name.firstName}
size={small ? 32 : 42}
/>
) : (
<UserName currentUserId={''} {...user} isVerified />
)}
</div>

<div className="small text-center halfem-margin-top">
<Link to={EDIT_USER_URI_FACTORY(user.id)}>
<Icon icon="edit" className="text-warning" gapRight={!small} />
{!small && <FormattedMessage id="generic.settings" defaultMessage="Settings" />}
</Link>

{small && <br />}

<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tokenExpiration">
<FormattedMessage id="app.badge.sessionExpiration" defaultMessage="Session expiration:" />{' '}
<FormattedRelative value={expiration} />
</Tooltip>
}>
<a
href="#"
onClick={e => {
e.preventDefault();
logout();
}}>
<Icon icon="sign-out-alt" className="text-danger" largeGapLeft={!small} gapRight={!small} />
{!small && <FormattedMessage id="app.logout" defaultMessage="Logout" />}
</a>
</OverlayTrigger>

{small && <br />}

{isSuperadminRole(user.privateData.role) && (
<a
href="#"
onClick={e => {
e.preventDefault();
this.openEffectiveRoleDialog();
}}>
<Icon icon="user" className="text-primary" largeGapLeft={!small} gapRight={!small} />
{!small && <FormattedMessage id="generic.role" defaultMessage="Role" />}
</a>
)}
</div>
</div>
</div>

{isSuperadminRole(user.privateData.role) && (
<Modal
show={this.state.effectiveRoleDialogOpened}
backdrop="static"
onHide={this.closeEffectiveRoleDialog}
bsSize="large">
<Modal.Header closeButton>
<Modal.Title>
<FormattedMessage id="app.badge.effectiveRoleDialog.title" defaultMessage="Change Effective Role" />
</Modal.Title>
</Modal.Header>
<Modal.Body>
<UserName currentUserId={user.id} {...user} large size={45} />
<EffectiveRoleSwitching
effectiveRole={effectiveRole}
updating={this.state.effectiveRoleUpdating}
setEffectiveRole={this.setEffectiveRole}
/>
</Modal.Body>
</Modal>
)}
</React.Fragment>
);
}
}

Badge.propTypes = {
id: PropTypes.string.isRequired,
fullName: PropTypes.string.isRequired,
name: PropTypes.shape({ firstName: PropTypes.string.isRequired }).isRequired,
avatarUrl: PropTypes.string,
expiration: PropTypes.number.isRequired,
privateData: PropTypes.shape({ settings: PropTypes.object.isRequired }).isRequired,
user: PropTypes.object.isRequired,
effectiveRole: PropTypes.string,
setEffectiveRole: PropTypes.func.isRequired,
logout: PropTypes.func,
size: PropTypes.number,
expiration: PropTypes.number.isRequired,
small: PropTypes.bool,
links: PropTypes.object,
};

Expand Down
Loading

0 comments on commit 1474a04

Please sign in to comment.