Skip to content

Commit

Permalink
STCOR-689 STCOR-690 Support switch consortium active affiliation (#1308)
Browse files Browse the repository at this point in the history
  • Loading branch information
NikitaSedyx committed May 10, 2023
1 parent 7c3e75b commit 15d2ee7
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* Unpin `moment` to reflect what is provided in platforms. Refs STCOR-706, STRIPES-678.
* Expose additional functionality so it can be consumed via other modules. Refs STCOR-711.
* Apps icons replaced with blue rectangle on apps menu. Refs STCOR-707.
* Display consortium active affiliation in the profile dropdown trigger. Refs STCOR-689.
* Support switch consortium active affiliation. Refs STCOR-690.

## [9.0.0](https://github.com/folio-org/stripes-core/tree/v9.0.0) (2023-01-30)
[Full Changelog](https://github.com/folio-org/stripes-core/compare/v8.3.0...v9.0.0)
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export { useModules } from './src/ModulesContext';
export { withModule, withModules } from './src/components/Modules';
export { default as stripesConnect } from './src/stripesConnect';
export { default as Pluggable } from './src/Pluggable';
export { updateUser } from './src/loginServices';
export { updateUser, updateTenant } from './src/loginServices';
export { default as coreEvents } from './src/events';
export { default as useOkapiKy } from './src/useOkapiKy';
export { default as withOkapiKy } from './src/withOkapiKy';
Expand Down
1 change: 1 addition & 0 deletions src/components/HandlerManager/HandlerManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class HandlerManager extends React.Component {
constructor(props) {
super(props);
const { event, stripes, modules, data } = props;

this.components = getEventHandlers(event, stripes, modules.handler, data);
}

Expand Down
15 changes: 11 additions & 4 deletions src/components/MainNav/ProfileDropdown/ProfileDropdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@

.button__label {
margin: 0 0.35rem;
display: inline-block;
display: inline-flex;
flex-direction: column;
align-items: start;
max-width: 15rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

& span {
width: 100%;
text-align: start;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
67 changes: 47 additions & 20 deletions src/components/MainNav/ProfileDropdown/ProfileDropdown.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { Component } from 'react';
import { isFunction, kebabCase } from 'lodash';
import get from 'lodash/get';
import { compose } from 'redux';
import { withRouter } from 'react-router';
import PropTypes from 'prop-types';
Expand Down Expand Up @@ -60,11 +59,7 @@ class ProfileDropdown extends Component {
this.createHandlerComponent = this.createHandlerComponent.bind(this);
this.navigateByUrl = this.navigateByUrl.bind(this);

const modulesWithLinks = this.getModulesWithLinks();
this.userLinks = modulesWithLinks.reduce((acc, m) => {
const links = m.links.userDropdown.map((link, index) => this.createLink(link, index, m));
return acc.concat(links);
}, []);
this.userLinks = this.getDropdownMenuLinks();
}

setInitialState(callback) {
Expand All @@ -73,9 +68,24 @@ class ProfileDropdown extends Component {
}, callback);
}

getModulesWithLinks() {
getDropdownMenuLinks = () => {
const modulesWithLinks = this.getModulesWithLinks();

return modulesWithLinks.reduce((acc, m) => {
const links = m.links.userDropdown.map((link, index) => this.createLink(link, index, m));
return acc.concat(links);
}, []);
}

getModulesWithLinks = () => {
const { modules } = this.props;
return ([].concat(...Object.values(modules)))
return Object.values(modules)
.flat()
.filter((module, index, self) => {
return index === self.findIndex((m) => (
m.module === module.module
));
})
.filter(({ links }) => links && Array.isArray(links.userDropdown));
}

Expand Down Expand Up @@ -122,6 +132,9 @@ class ProfileDropdown extends Component {
}

toggleDropdown() {
// Get items after rechecking for item visibility
this.userLinks = this.getDropdownMenuLinks();

this.setState(({ dropdownOpen }) => ({
dropdownOpen: !dropdownOpen
}));
Expand All @@ -145,7 +158,8 @@ class ProfileDropdown extends Component {
alt={user.name}
ariaLabel={user.name}
className={css.avatar}
/>);
/>
);
}

navigateByUrl(link) {
Expand All @@ -168,7 +182,7 @@ class ProfileDropdown extends Component {
* if setting is active in stripes config
*/
let perms = null;
if (stripes.config && stripes.config.showPerms) {
if (stripes.config?.showPerms) {
perms = (
<IntlConsumer>
{
Expand Down Expand Up @@ -209,7 +223,7 @@ class ProfileDropdown extends Component {
<NavList>
<NavListSection>
{
(!stripes.config || !stripes.config.showHomeLink) ?
(!stripes.config?.showHomeLink) ?
null :
<NavListItem id="clickable-home" type="button" onClick={this.onHome}>
<FormattedMessage id="stripes-core.front.home" />
Expand All @@ -228,27 +242,40 @@ class ProfileDropdown extends Component {

renderProfileTrigger = ({ getTriggerProps, open }) => {
const { intl } = this.props;
const servicePointName = get(this.getUserData(), 'curServicePoint.name', null);

return (
<NavButton
ariaLabel={intl.formatMessage({ id: 'stripes-core.mainnav.myProfileAriaLabel' })}
selected={open}
className={css.button}
icon={this.getProfileImage()}
label={servicePointName ? (
<>
<span className={css.button__label}>
{servicePointName}
</span>
<Icon icon={open ? 'caret-up' : 'caret-down'} />
</>
) : null}
label={this.renderProfileTriggerLabel({ open })}
{...getTriggerProps()}
/>
);
}

renderProfileTriggerLabel = ({ open }) => {
const { okapi } = this.props.stripes;
const userData = this.getUserData();
const servicePointName = userData?.curServicePoint?.name;
const tenantName = userData?.tenants?.find(({ id }) => id === okapi.tenant)?.name;

const hasLabel = Boolean(servicePointName || tenantName);

return (
hasLabel ? (
<>
<span className={css.button__label}>
{tenantName && <span>{tenantName}</span>}
{servicePointName && <span>{servicePointName}</span>}
</span>
<Icon icon={open ? 'caret-up' : 'caret-down'} />
</>
) : null
);
}

renderProfileMenu = ({ open }) => (
<DropdownMenu open={open}>
{this.getDropdownContent()}
Expand Down
83 changes: 83 additions & 0 deletions src/components/MainNav/ProfileDropdown/ProfileDropdown.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';

import { ModulesContext } from '../../../ModulesContext';
import TestComponent from './ProfileDropdown';

jest.unmock('@folio/stripes-components');
jest.mock('currency-codes/data', () => ({ filter: () => [] }));

const checkAction = jest.fn(() => true);
const eventHandler = jest.fn(() => 'Handler content');
const modules = {
app: [
{
displayName: 'Test app',
handlerName: 'eventHandler',
route: '/test',
links: {
userDropdown: [{
event: 'TEST_EVENT',
caption: 'Profile dropdown action',
check: 'checkAction',
}]
},
getModule: jest.fn(() => ({
checkAction,
eventHandler,
})),
},
],
};

const tenant = 'test';
const stripes = {
user: {
user: {
id: 'user-id',
tenants: [{
id: tenant,
name: 'Central office',
}]
},
},
okapi: {
tenant,
},
};

const defaultProps = {
onLogout: jest.fn(),
stripes,
};

const wrapper = ({ children }) => (
<MemoryRouter>
<ModulesContext.Provider value={modules}>
{children}
</ModulesContext.Provider>
</MemoryRouter>
);

const renderProfileDropdown = (props = {}) => render(
<TestComponent
{...defaultProps}
{...props}
/>,
{ wrapper },
);

describe('ProfileDropdown', () => {
it('should display current consortium (if enabled) in the dropdown trigger', () => {
renderProfileDropdown();

expect(screen.getByText('Central office')).toBeInTheDocument();
});

it('should display module profile dropdown item', () => {
renderProfileDropdown();

expect(checkAction).toBeCalled();
expect(screen.getByText('Profile dropdown action')).toBeInTheDocument();
});
});
30 changes: 26 additions & 4 deletions src/loginServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,13 @@ export function createOkapiSession(okapiUrl, store, tenant, token, data) {
// permission-names for keys and `true` for values
const perms = Object.assign({}, ...data.permissions.permissions.map(p => ({ [p.permissionName]: true })));
store.dispatch(setCurrentPerms(perms));

const sessionTenant = data.tenant || tenant;
const okapiSess = {
token,
user,
perms,
tenant: sessionTenant,
};

return localforage.setItem('loginResponse', data)
Expand All @@ -390,13 +393,14 @@ export function createOkapiSession(okapiUrl, store, tenant, token, data) {
* @returns {Promise}
*/
export function validateUser(okapiUrl, store, tenant, session) {
return fetch(`${okapiUrl}/bl-users/_self`, { headers: getHeaders(tenant, session.token) }).then((resp) => {
const { token, user, perms, tenant: sessionTenant = tenant } = session;

return fetch(`${okapiUrl}/bl-users/_self`, { headers: getHeaders(sessionTenant, token) }).then((resp) => {
if (resp.ok) {
const { token, user, perms } = session;
return resp.json().then((data) => {
store.dispatch(setLoginData(data));
store.dispatch(setSessionData({ token, user, perms }));
return loadResources(okapiUrl, store, tenant, user.id);
store.dispatch(setSessionData({ token, user, perms, tenant: sessionTenant }));
return loadResources(okapiUrl, store, sessionTenant, user.id);
});
} else {
store.dispatch(clearCurrentUser());
Expand Down Expand Up @@ -623,3 +627,21 @@ export function updateUser(store, data) {
store.dispatch(updateCurrentUser(data));
});
}

/**
* updateTenant
* 1. concat the given data onto local-storage tenant and save it
* 2. update full user info based on new tenant
* @param {string} okapiUrl okapi url
* @param {redux-store} store redux store
* @param {object} data
*
* @returns {Promise}
*/
export async function updateTenant(okapi, store, tenant) {
const okapiSess = await localforage.getItem('okapiSess');

await localforage.setItem('okapiSess', { ...okapiSess, tenant });

await requestUserWithPerms(okapi.url, store, tenant, okapi.token);
}
Loading

0 comments on commit 15d2ee7

Please sign in to comment.