Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UIIN-2590: ECS: Show info message when user in member tenant tries to view shared instance details without permission #2328

Merged
merged 3 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* Create new instance success toast no longer shows the instance HRID. Fixes UIIN-2635.
* Optimistic locking message not working for instances in non-consortial tenant. Fixes UIIN-2628.
* Add immediate warning message when a local instance is shared. Refs UIIN-2617.
* ECS: Show info message when user in member tenant tries to view shared instance details without permission. Refs UIIN-2590.

## [10.0.0](https://github.com/folio-org/ui-inventory/tree/v10.0.0) (2023-10-13)
[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.12...v10.0.0)
Expand Down
83 changes: 17 additions & 66 deletions src/Instance/InstanceDetails/InstanceDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
PaneMenu,
Row,
MessageBanner,
Icon,
} from '@folio/stripes/components';

import { InstanceTitle } from './InstanceTitle';
Expand Down Expand Up @@ -71,8 +70,6 @@ const InstanceDetails = forwardRef(({
tagsEnabled,
userTenantPermissions,
isShared,
isLoading,
isInstanceSharing,
...rest
}, ref) => {
const intl = useIntl();
Expand All @@ -86,15 +83,12 @@ const InstanceDetails = forwardRef(({
const accordionState = useMemo(() => getAccordionState(instance, accordions), [instance]);
const [helperApp, setHelperApp] = useState();

const isBasicPane = isInstanceSharing || isLoading;
const canCreateHoldings = stripes.hasPerm('ui-inventory.holdings.create');
const tags = instance?.tags?.tagList;
const isUserInCentralTenant = checkIfUserInCentralTenant(stripes);

const canCreateHoldings = stripes.hasPerm('ui-inventory.holdings.create');
const isConsortialHoldingsVisible = instance?.shared || isInstanceShadowCopy(instance?.source);

const detailsLastMenu = useMemo(() => {
if (isBasicPane) return null;

return (
<PaneMenu>
{
Expand All @@ -110,15 +104,9 @@ const InstanceDetails = forwardRef(({
}
</PaneMenu>
);
}, [isBasicPane, tagsEnabled, tags, intl]);
const detailsActionMenu = useMemo(
() => (isBasicPane ? null : actionMenu),
[isBasicPane, actionMenu],
);
}, [tagsEnabled, tags, intl]);

const renderPaneTitle = () => {
if (isBasicPane) return intl.formatMessage({ id: 'ui-inventory.edit' });

const isInstanceShared = Boolean(isShared || isInstanceShadowCopy(instance?.source));

return (
Expand All @@ -134,8 +122,6 @@ const InstanceDetails = forwardRef(({
};

const renderPaneSubtitle = () => {
if (isBasicPane) return null;

return (
<FormattedMessage
id="ui-inventory.instanceRecordSubtitle"
Expand All @@ -147,32 +133,20 @@ const InstanceDetails = forwardRef(({
);
};

const renderDetails = () => {
if (isInstanceSharing) {
return (
<div>
<MessageBanner show={Boolean(isInstanceSharing)} type="warning">
<FormattedMessage id="ui-inventory.warning.instance.sharingLocalInstance" />
</MessageBanner>
</div>
);
}

if (isLoading) {
return (
<div>
<Icon
icon="spinner-ellipsis"
width="100px"
/>
</div>
);
}

const isConsortialHoldingsVisible = instance?.shared || isInstanceShadowCopy(instance?.source);

return (
<>
return (
<>
<Pane
{...rest}
data-test-instance-details
appIcon={<AppIcon app="inventory" iconKey="instance" />}
paneTitle={renderPaneTitle()}
paneSub={renderPaneSubtitle()}
actionMenu={actionMenu}
lastMenu={detailsLastMenu}
dismissible
onClose={onClose}
defaultWidth="fill"
>
<TitleManager record={instance.title} />

<AccordionStatus ref={ref}>
Expand Down Expand Up @@ -293,25 +267,6 @@ const InstanceDetails = forwardRef(({
/>
</AccordionSet>
</AccordionStatus>
</>
);
};

return (
<>
<Pane
{...rest}
data-test-instance-details
appIcon={<AppIcon app="inventory" iconKey="instance" />}
paneTitle={renderPaneTitle()}
paneSub={renderPaneSubtitle()}
actionMenu={detailsActionMenu}
lastMenu={detailsLastMenu}
dismissible
onClose={onClose}
defaultWidth="fill"
>
{renderDetails()}
</Pane>
{ helperApp && <HelperApp appName={helperApp} onClose={setHelperApp} />}
</>
Expand All @@ -326,16 +281,12 @@ InstanceDetails.propTypes = {
tagsToggle: PropTypes.func,
tagsEnabled: PropTypes.bool,
userTenantPermissions: PropTypes.arrayOf(PropTypes.object),
isLoading: PropTypes.bool,
isInstanceSharing: PropTypes.bool,
isShared: PropTypes.bool,
};

InstanceDetails.defaultProps = {
instance: {},
tagsEnabled: false,
isLoading: false,
isInstanceSharing: false,
isShared: false,
};

Expand Down
7 changes: 0 additions & 7 deletions src/Instance/InstanceDetails/InstanceDetails.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,6 @@ describe('InstanceDetails', () => {
expect(screen.getByText('Instance relationship (analytics and bound-with)')).toBeInTheDocument();
});

it('should show a correct Warning message banner when instance sharing is in progress', () => {
renderInstanceDetails({ isInstanceSharing: true });

expect(screen.getByText('Sharing this local instance will take a few moments.' +
' A success message and updated details will be displayed upon completion.')).toBeInTheDocument();
});

it('should show a correct Warning message banner when staff suppressed', () => {
const staffSuppressedInstance = {
...instance,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';

import { AppIcon } from '@folio/stripes/core';
import {
Pane,
Icon,
} from '@folio/stripes/components';

const InstanceLoadingPane = ({ onClose }) => {
const intl = useIntl();

return (
<Pane
data-test-instance-details
appIcon={<AppIcon app="inventory" iconKey="instance" />}
paneTitle={intl.formatMessage({ id: 'ui-inventory.edit' })}
dismissible
onClose={onClose}
defaultWidth="fill"
>
<Icon
icon="spinner-ellipsis"
width="100px"
/>
</Pane>
);
};

InstanceLoadingPane.propTypes = { onClose: PropTypes.func.isRequired };

export default InstanceLoadingPane;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
fireEvent,
screen,
} from '@folio/jest-config-stripes/testing-library/react';

import '../../../../test/jest/__mock__';

import { Icon } from '@folio/stripes/components';

import {
renderWithIntl,
translationsProperties,
} from '../../../../test/jest/helpers';

import InstanceLoadingPane from './InstanceLoadingPane';

Icon.mockClear().mockImplementation(({ icon }) => <span>{icon}</span>);

const mockOnClose = jest.fn();

const renderInstanceLoadingPane = () => {
const component = <InstanceLoadingPane onClose={mockOnClose} />;

return renderWithIntl(component, translationsProperties);
};

describe('InstanceLoadingPane', () => {
it('should render loading spinner', () => {
renderInstanceLoadingPane();

expect(screen.getByText('spinner-ellipsis')).toBeInTheDocument();
});

it('should render correct header', () => {
renderInstanceLoadingPane();

expect(screen.getByText('Edit')).toBeInTheDocument();
});

it('should not render action menu', () => {
renderInstanceLoadingPane();

expect(screen.queryByRole('button', { name: 'Actions' })).not.toBeInTheDocument();
});

it('should call onClose cb when pane is closed', () => {
renderInstanceLoadingPane();

fireEvent.click(screen.getByRole('button', { name: 'Close Edit' }));

expect(mockOnClose).toHaveBeenCalled();
});
});
1 change: 1 addition & 0 deletions src/Instance/InstanceDetails/InstanceLoadingPane/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './InstanceLoadingPane';
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';

import { AppIcon } from '@folio/stripes/core';
import {
Pane,
MessageBanner,
} from '@folio/stripes/components';

const InstanceWarningPane = ({
onClose,
messageBannerText,
}) => {
const intl = useIntl();

return (
<Pane
data-test-instance-details
appIcon={<AppIcon app="inventory" iconKey="instance" />}
paneTitle={intl.formatMessage({ id: 'ui-inventory.edit' })}
dismissible
onClose={onClose}
defaultWidth="fill"
>
<MessageBanner type="warning">
{messageBannerText}
</MessageBanner>
</Pane>
);
};

InstanceWarningPane.propTypes = {
onClose: PropTypes.func.isRequired,
messageBannerText: PropTypes.oneOfType([PropTypes.element, PropTypes.string]).isRequired,
};

export default InstanceWarningPane;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
fireEvent,
screen,
} from '@folio/jest-config-stripes/testing-library/react';

import '../../../../test/jest/__mock__';

import { Icon } from '@folio/stripes/components';

import {
renderWithIntl,
translationsProperties,
} from '../../../../test/jest/helpers';

import InstanceWarningPane from './InstanceWarningPane';

Icon.mockClear().mockImplementation(({ icon }) => <span>{icon}</span>);

const mockOnClose = jest.fn();

const renderInstanceLoadingPane = (messageBannerText = 'warning text') => {
const component = (
<InstanceWarningPane
onClose={mockOnClose}
messageBannerText={messageBannerText}
/>
);

return renderWithIntl(component, translationsProperties);
};

describe('InstanceWarningPane', () => {
it('should render warning banner', () => {
renderInstanceLoadingPane();

expect(screen.getByText('warning text')).toBeInTheDocument();
});

it('should render correct header', () => {
renderInstanceLoadingPane();

expect(screen.getByText('Edit')).toBeInTheDocument();
});

it('should not render action menu', () => {
renderInstanceLoadingPane();

expect(screen.queryByRole('button', { name: 'Actions' })).not.toBeInTheDocument();
});

it('should call onClose cb when pane is closed', () => {
renderInstanceLoadingPane();

fireEvent.click(screen.getByRole('button', { name: 'Close Edit' }));

expect(mockOnClose).toHaveBeenCalled();
});
});
1 change: 1 addition & 0 deletions src/Instance/InstanceDetails/InstanceWarningPane/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './InstanceWarningPane';
2 changes: 2 additions & 0 deletions src/Instance/InstanceDetails/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export * from './InstanceTitle';
export * from './InstanceNotesView';
export * from './InstanceNewHolding';

export { default as InstanceLoadingPane } from './InstanceLoadingPane';
export { default as InstanceWarningPane } from './InstanceWarningPane';
export { default as SubInstanceList } from './SubInstanceList';
export { default as InstanceDetails } from './InstanceDetails';
export { default as SubInstanceGroup } from './SubInstanceGroup';
Loading
Loading