Skip to content

Commit

Permalink
UIU-3110 show roles assigned to users
Browse files Browse the repository at this point in the history
half-baked draft

Refs UIU-3110
  • Loading branch information
zburke committed Apr 5, 2024
1 parent 51f6114 commit fc0e85f
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* UX consistency: Use Save & close button label stripes-component translation key. Refs UIU-3078.
* Fix two "triangle down" icons in select element of "Copy existing fee/fine owner table entries" modal. Refs UIU-2929.
* Show Roles assigned to users. Refs UIU-3110.

## [10.1.0](https://github.com/folio-org/ui-users/tree/v10.1.0) (2024-03-20)
[Full Changelog](https://github.com/folio-org/ui-users/compare/v10.0.4...v10.1.0)
Expand Down
109 changes: 109 additions & 0 deletions src/components/RenderRoles/RenderRoles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import _ from 'lodash';
import React from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import {
List,
Accordion,
Badge,
Headline,
Loading
} from '@folio/stripes/components';

import AffiliationsSelect from '../AffiliationsSelect/AffiliationsSelect';
import IfConsortium from '../IfConsortium';
import IfConsortiumPermission from '../IfConsortiumPermission';
import { affiliationsShape } from '../../shapes';

class RenderRoles extends React.Component {
static propTypes = {
accordionId: PropTypes.string,
affiliations: affiliationsShape,
expanded: PropTypes.bool,
heading: PropTypes.node.isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}),
isLoading: PropTypes.bool,
listedRoles: PropTypes.arrayOf(PropTypes.object),
onChangeAffiliation: PropTypes.func,
onToggle: PropTypes.func,
permToRead: PropTypes.string.isRequired,
selectedAffiliation: PropTypes.string,
stripes: PropTypes.shape({
hasPerm: PropTypes.func.isRequired,
config: PropTypes.shape({
showPerms: PropTypes.bool,
listInvisiblePerms: PropTypes.bool,
}).isRequired,
}).isRequired,
};

static defaultProps = {
onChangeAffiliation: _.noop,
isLoading: false,
}

renderList() {
const {
listedRoles,
} = this.props;
const listFormatter = item => <li key={item.id}>{item.name}</li>;
const noPermissionsFound = <FormattedMessage id="ui-users.roles.empty" />;

return (
<List
items={(listedRoles || []).sort((a, b) => a.name.localeCompare(b.name))}
itemFormatter={listFormatter}
isEmptyMessage={noPermissionsFound}
/>
);
}

render() {
const {
affiliations,
accordionId,
expanded,
isLoading,
onChangeAffiliation,
onToggle,
listedRoles,
stripes,
permToRead,
selectedAffiliation,
heading,
} = this.props;

if (!stripes.hasPerm(permToRead)) { return null; }

return (
<Accordion
open={expanded}
id={accordionId}
onToggle={onToggle}
label={<Headline size="large" tag="h3">{heading}</Headline>}
displayWhenClosed={
isLoading ? <Loading /> : <Badge>{listedRoles.length}</Badge>
}
>
<IfConsortium>
<IfConsortiumPermission perm="consortia.user-tenants.collection.get">
{Boolean(affiliations?.length) && (
<AffiliationsSelect
affiliations={affiliations}
onChange={onChangeAffiliation}
isLoading={isLoading}
value={selectedAffiliation}
/>
)}
</IfConsortiumPermission>
</IfConsortium>

{this.renderList()}
</Accordion>
);
}
}

export default injectIntl(RenderRoles);
74 changes: 74 additions & 0 deletions src/components/RenderRoles/RenderRoles.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import renderWithRouter from 'helpers/renderWithRouter';
import RenderRoles from './RenderRoles';


jest.unmock('@folio/stripes/components');

const renderRenderRoles = (props) => renderWithRouter(<RenderRoles {...props} />);
const STRIPES = {
config: {},
hasPerm: jest.fn().mockReturnValue(true),
};

const STRIPESWITHOUTPERMISSION = {
config: {},
hasPerm: jest.fn().mockReturnValue(false),
};

describe('render RenderRoles component', () => {
it('Component must be rendered', () => {
const props = {
accordionId: 'assignedRoles',
expanded: true,
onToggle: jest.fn(),
heading: <div>Assigned roles</div>,
permToRead: 'perms.permissions.get',
listedRoles: [
{
'id': '024f7895-45fa-4ea7-ba06-a6a51758559f',
'name': 'funky',
'description': 'get down get down'
},
{
'id': '27b6cf82-303a-4737-b2d8-c5bc807f077f',
'name': 'chicken',
'description': 'look up look up, the sky is falling!'
}
],
intl: {},
stripes: STRIPES,
};
renderRenderRoles(props);
expect(renderRenderRoles(props)).toBeTruthy();
});

it('Checking for roles', () => {
const props = {
accordionId: 'assignedRoles',
expanded: true,
onToggle: jest.fn(),
heading: <div>Assigned Permissions</div>,
permToRead: 'perms.permissions.get',
listedPermissions: [],
intl: {},
stripes: STRIPESWITHOUTPERMISSION,
};
renderRenderRoles(props);
expect(renderRenderRoles(props)).toBeTruthy();
});

it('Passing empty props', () => {
const props = {
accordionId: 'assignedRoles',
expanded: true,
onToggle: jest.fn(),
heading: <div>Assigned Permissions</div>,
permToRead: 'perms.permissions.get',
listedRoles: [],
intl: {},
stripes: STRIPES,
};
renderRenderRoles(props);
expect(renderRenderRoles(props)).toBeTruthy();
});
});
1 change: 1 addition & 0 deletions src/components/RenderRoles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './RenderRoles';
55 changes: 55 additions & 0 deletions src/components/UserDetailSections/UserRoles/UserRoles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';

import {
useUserAffiliations,
useUserTenantRoles,
} from '../../../hooks';
import RenderRoles from '../../RenderRoles';
import { isAffiliationsEnabled } from '../../util';

const propTypes = {
stripes: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
};

const UserRoles = (props) => {
const { stripes, user } = props;
const { id: userId } = useParams();
const [tenantId, setTenantId] = useState(stripes.okapi.tenant);

const {
affiliations,
isFetching: isAffiliationsFetching,
} = useUserAffiliations({ userId }, { enabled: isAffiliationsEnabled(user) });

const {
userRoles,
isFetching: isPermissionsFetching,
} = useUserTenantRoles({ userId, tenantId });

const isLoading = isAffiliationsFetching || isPermissionsFetching;

useEffect(() => {
if (!affiliations.some(({ tenantId: assigned }) => tenantId === assigned)) {
setTenantId(stripes.okapi.tenant);
}
}, [affiliations, stripes.okapi.tenant, tenantId]);

return (<RenderRoles
{...props}
heading={<FormattedMessage id="ui-users.roles.userRoles" />}
permToRead="roles.users.collection.get"
affiliations={affiliations}
selectedAffiliation={tenantId}
isLoading={isLoading}
onChangeAffiliation={setTenantId}
listedRoles={userRoles || []}
/>);
};

UserRoles.propTypes = propTypes;

export default UserRoles;
87 changes: 87 additions & 0 deletions src/components/UserDetailSections/UserRoles/UserRoles.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import userEvent from '@folio/jest-config-stripes/testing-library/user-event';

Check warning on line 1 in src/components/UserDetailSections/UserRoles/UserRoles.test.js

View workflow job for this annotation

GitHub Actions / build-npm

'userEvent' is defined but never used. Allowed unused vars must match /React/u

Check warning on line 1 in src/components/UserDetailSections/UserRoles/UserRoles.test.js

View workflow job for this annotation

GitHub Actions / build-npm

'userEvent' is defined but never used. Allowed unused vars must match /React/u
import { screen } from '@folio/jest-config-stripes/testing-library/react';

import '__mock__/matchMedia.mock';
import renderWithRouter from 'helpers/renderWithRouter';
import affiliations from 'fixtures/affiliations';
import roles from 'fixtures/roles';
import {
useUserAffiliations,
useUserTenantRoles,
} from '../../../hooks';
import IfConsortiumPermission from '../../IfConsortiumPermission';
import UserRoles from './UserRoles';

jest.unmock('@folio/stripes/components');
jest.mock('../../../hooks', () => ({
useUserAffiliations: jest.fn(),
useUserTenantRoles: jest.fn(),
}));
jest.mock('../../IfConsortium', () => jest.fn(({ children }) => <>{children}</>));
jest.mock('../../IfConsortiumPermission', () => jest.fn());

const STRIPES = {
config: {},
hasPerm: jest.fn().mockReturnValue(true),
okapi: {
tenant: 'diku',
},
user: {
user: {
consortium: {},
},
},
};

const defaultProps = {
accordionId: 'assignedRoles',
expanded: true,
onToggle: jest.fn(),
heading: <div>User roles</div>,
permToRead: 'perms.permissions.get',
intl: {},
stripes: STRIPES,
user: {},
};

const renderUserRoles = (props = {}) => renderWithRouter(
<UserRoles
{...defaultProps}
{...props}
/>
);

describe('UserRoles component', () => {
beforeEach(() => {
useUserAffiliations.mockClear().mockReturnValue({ isFetching: false, affiliations });
useUserTenantRoles.mockClear().mockImplementation(({ tenantId }) => ({
isFetching: false,
userRoles: tenantId === 'diku' ? roles : [],
}));
});

it('should render user roles accordion', () => {
IfConsortiumPermission.mockReturnValue(null);
renderUserRoles();

expect(screen.getByText('ui-users.roles.userRoles')).toBeInTheDocument();
expect(screen.getByText('funky')).toBeInTheDocument();
expect(screen.getByText('chicken')).toBeInTheDocument();
});

// describe('Consortia', () => {
// it('should update roles list after selecting another affiliation', async () => {
// IfConsortiumPermission.mockImplementation(({ children }) => children);
// renderUserRoles();

// expect(screen.getByText('funky')).toBeInTheDocument();
// expect(screen.getByText('chicken')).toBeInTheDocument();

// await userEvent.click(screen.getByText(affiliations[1].tenantName));

// expect(screen.getByText('funky')).not.toBeInTheDocument();
// expect(screen.getByText('chicken')).not.toBeInTheDocument();
// expect(screen.getByText('ui-users.roles.empty')).toBeInTheDocument();
// });
// });
});
1 change: 1 addition & 0 deletions src/components/UserDetailSections/UserRoles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './UserRoles';
1 change: 1 addition & 0 deletions src/components/UserDetailSections/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export { default as UserRequests } from './UserRequests';
export { default as UserAccounts } from './UserAccounts';
export { default as UserAffiliations } from './UserAffiliations';
export { default as UserPermissions } from './UserPermissions';
export { default as UserRoles } from './UserRoles';
export { default as UserServicePoints } from './UserServicePoints';
export { default as RequestPreferencesView } from './ExtendedInfo/components/RequestPreferencesView';
1 change: 1 addition & 0 deletions src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as useToggle } from './useToggle';
export { default as useUserAffiliations } from './useUserAffiliations';
export { default as useUserAffiliationsMutation } from './useUserAffiliationsMutation';
export { default as useUserTenantPermissions } from './useUserTenantPermissions';
export { default as useUserTenantRoles } from './useUserTenantRoles';
export { default as useProfilePicture } from './useProfilePicture';
export { default as useLocalizedCurrency } from './useLocalizedCurrency';

1 change: 1 addition & 0 deletions src/hooks/useUserTenantRoles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './useUserTenantRoles';
Loading

0 comments on commit fc0e85f

Please sign in to comment.