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

UISAUTHCOM-2: Move component CapabilitiesSection from ui-authorization-roles #3

Merged
merged 7 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Change history for stripes-authorization-components

## (1.0.0 IN PROGRESS)

* [UISAUTHCOM-2](https://folio-org.atlassian.net/browse/UISAUTHCOM-2) Move component CapabilitiesSection from ui-authorization-roles.
105 changes: 105 additions & 0 deletions lib/Capabilities/CapabilitiesDataType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useCallback, useMemo } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import PropTypes from 'prop-types';

import { MultiColumnList, Headline } from '@folio/stripes/components';

import { capabilitiesPropType } from './types';
import { columnTranslations } from '../constants/translations';
import ItemActionCheckbox from './ItemActionCheckbox';

const CapabilitiesDataType = ({ content, readOnly, onChangeCapabilityCheckbox, isCapabilitySelected, isCapabilityDisabled }) => {
const { formatMessage } = useIntl();
/**
* Renders an action checkbox for an item.
*
* @param {Object} item - The capability item object.
* @param {string} action - The action to render checkbox for action(e.g. 'view').
* @return {JSX.Element} By requirements on non-readonly mode we should hide the checkboxes on
* not related actions. Since every capability have only one action
* (e.g. {...capability, action: 'view'}),
* show only checkbox for that particular action.
* If readOnly mode we should show the checkbox for all actions.
*/
const renderItemActionCheckbox = useCallback((item, action) => {
return (
<ItemActionCheckbox
action={action}
readOnly={readOnly}
isCapabilityDisabled={isCapabilityDisabled}
onChangeCapabilityCheckbox={onChangeCapabilityCheckbox}
item={item}
isCapabilitySelected={isCapabilitySelected}
/>
);
}, [readOnly, isCapabilityDisabled, onChangeCapabilityCheckbox, isCapabilitySelected]);


const columnMapping = useMemo(() => ({
application: formatMessage(columnTranslations.application),
resource: formatMessage(columnTranslations.resource),
view: formatMessage(columnTranslations.view),
edit: formatMessage(columnTranslations.edit),
create: formatMessage(columnTranslations.create),
delete: formatMessage(columnTranslations.delete),
manage: formatMessage(columnTranslations.manage),
// policies: formatMessage(columnTranslations.policies)
}), [formatMessage]);

const resultsFormatter = useMemo(() => ({
application: (item) => item.applicationId,
resource: (item) => item.resource,
view: (item) => renderItemActionCheckbox(item, 'view'),
edit: (item) => renderItemActionCheckbox(item, 'edit'),
create: (item) => renderItemActionCheckbox(item, 'create'),
delete: (item) => renderItemActionCheckbox(item, 'delete'),
manage: (item) => renderItemActionCheckbox(item, 'manage'),
// policies: (item) => <Badge>{item.policiesCount}</Badge>
}), [renderItemActionCheckbox]);

const visibleColumns = [
'application',
'resource',
'view',
'edit',
'create',
'delete',
'manage',
'',
];

const columnWidths = {
application: '40%',
resource: '24%',
view: '6%',
edit: '6%',
create: '6%',
delete: '6%',
manage: '6%',
};

return (
<div data-testid="capabilities-data-type">
<Headline size="large" margin="none" tag="h3">
<FormattedMessage id="stripes-authorization-components.details.data" />
</Headline>
<MultiColumnList
interactive={false}
columnMapping={columnMapping}
formatter={resultsFormatter}
contentData={content}
visibleColumns={visibleColumns}
columnWidths={columnWidths}
/>
</div>
);
};

CapabilitiesDataType.propTypes = { content: capabilitiesPropType,
UladzislauKutarkin marked this conversation as resolved.
Show resolved Hide resolved
readOnly: PropTypes.bool,
onChangeCapabilityCheckbox: PropTypes.func,
isCapabilitySelected: PropTypes.func,
isCapabilityDisabled: PropTypes.func };

export default CapabilitiesDataType;

51 changes: 51 additions & 0 deletions lib/Capabilities/CapabilitiesDataType.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import userEvent from '@folio/jest-config-stripes/testing-library/user-event';
import { render } from '@folio/jest-config-stripes/testing-library/react';
import '@folio/jest-config-stripes/testing-library/jest-dom';

import CapabilitiesDataType from './CapabilitiesDataType';

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

const dataTypeCapabilities = [
{
id: '8d2da27c-1d56-48b6-923358d-2bfae6d79dc8',
applicationId: 'Circulation',
name: 'foo_item.create',
description: 'Sample: Create foo item',
resource: 'Foo Item',
action: 'create',
type: 'data',
permissions: ['foo.item.post'],
actions: { view: 'view-id', create: 'create-id', edit: 'edit-id', delete: 'delete-id' },
},
];

const renderComponent = (data, onChange) => render(
<CapabilitiesDataType content={data} isCapabilitySelected={jest.fn().mockReturnValue(true)} onChangeCapabilityCheckbox={onChange} />
);

describe('Data capabilities type', () => {
afterAll(() => {
jest.clearAllMocks();
});

it('renders fields in the grid', () => {
const { getByText } = renderComponent(dataTypeCapabilities);

expect(getByText('Foo Item')).toBeInTheDocument();
expect(getByText('Circulation')).toBeInTheDocument();
});

it('renders checkboxes', async () => {
const mockChangeHandler = jest.fn();
const { getAllByRole, getByRole } = renderComponent(dataTypeCapabilities, mockChangeHandler);

expect(getAllByRole('checkbox')).toHaveLength(4);
expect(getByRole('checkbox', { name:/columns.view Foo Item/ })).toBeChecked();

await userEvent.click(getAllByRole('checkbox')[0]);

expect(mockChangeHandler).toHaveBeenCalled();
});
});
73 changes: 73 additions & 0 deletions lib/Capabilities/CapabilitiesProcedural.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, useIntl } from 'react-intl';

import { MultiColumnList, Headline } from '@folio/stripes/components';
import { getApplicationName } from '../utils/getApplicationName';
import { capabilitiesPropType } from './types';
import { columnTranslations } from '../constants/translations';

import css from './styles.css';
import ItemActionCheckbox from './ItemActionCheckbox';

const CapabilitiesProcedural = ({ content, readOnly, onChangeCapabilityCheckbox, isCapabilitySelected, isCapabilityDisabled }) => {
const { formatMessage } = useIntl();

const renderItemActionCheckbox = useCallback((item, action) => {
return (
<ItemActionCheckbox
action={action}
readOnly={readOnly}
isCapabilityDisabled={isCapabilityDisabled}
onChangeCapabilityCheckbox={onChangeCapabilityCheckbox}
item={item}
isCapabilitySelected={isCapabilitySelected}
/>
);
}, [readOnly, isCapabilityDisabled, onChangeCapabilityCheckbox, isCapabilitySelected]);

const columnMapping = useMemo(() => ({
application: formatMessage(columnTranslations.application),
resource: formatMessage(columnTranslations.settings),
execute: formatMessage(columnTranslations.execute),
// policies: formatMessage(columnTranslations.policies)
}), [formatMessage]);

const resultsFormatter = useMemo(() => ({
application: item => getApplicationName(item.applicationId),
resource: item => item.resource,
execute: item => renderItemActionCheckbox(item, 'execute'),
// policies: (item) => <Badge>{item.policiesCount}</Badge>
}), [renderItemActionCheckbox]);

const visibleColumns = ['application', 'resource', 'execute', ''];
const columnWidth = {
application: '40%',
resource: '48%',
execute: '6%'
};

return (
<div data-testid="capabilities-procedural-type" className={css.gutterTop}>
<Headline size="large" margin="none" tag="h3">
<FormattedMessage id="stripes-authorization-components.details.procedural" />
</Headline>
<MultiColumnList
interactive={false}
columnMapping={columnMapping}
formatter={resultsFormatter}
contentData={content}
visibleColumns={visibleColumns}
columnWidths={columnWidth}
/>
</div>
);
};

CapabilitiesProcedural.propTypes = { content: capabilitiesPropType,
readOnly: PropTypes.bool,
onChangeCapabilityCheckbox: PropTypes.func,
isCapabilitySelected: PropTypes.func,
isCapabilityDisabled: PropTypes.func };

export default CapabilitiesProcedural;
55 changes: 55 additions & 0 deletions lib/Capabilities/CapabilitiesProcedural.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import userEvent from '@folio/jest-config-stripes/testing-library/user-event';

import { render } from '@folio/jest-config-stripes/testing-library/react';
import CapabilitiesProcedural from './CapabilitiesProcedural';
import '@folio/jest-config-stripes/testing-library/jest-dom';

const proceduralTypeCapabilities = [
{
id: '8d2da27c-1d56-48b6-958d-2bfae6d7922dc8',
applicationId: 'Fees/fines-22.3',
name: 'foo_item.delete',
description: 'Login: Delete foo item',
resource: 'Settings source',
action: 'execute',
type: 'procedural',
permissions: ['foo.item.post'],
actions: { execute: 'execute-id' },
},
];

const renderComponent = (data, onChange) => render(
<CapabilitiesProcedural content={data} isCapabilitySelected={jest.fn().mockReturnValue(true)} onChangeCapabilityCheckbox={onChange} />
);

describe('Procedural capabilities type', () => {
afterAll(() => {
jest.clearAllMocks();
});
it('renders fields in the grid', () => {
const mockChangeHandler = jest.fn();
const { getByText } = renderComponent(proceduralTypeCapabilities, mockChangeHandler);
expect(getByText('Settings source')).toBeInTheDocument();
expect(getByText('Fees/fines')).toBeInTheDocument();
});

it('renders checkboxes', async () => {
const mockChangeHandler = jest.fn().mockReturnValue(true);
const { getAllByRole } = renderComponent(proceduralTypeCapabilities, mockChangeHandler);

expect(getAllByRole('checkbox')).toHaveLength(1);
expect(getAllByRole('checkbox')[0]).toBeChecked();

await userEvent.click(getAllByRole('checkbox')[0]);

expect(mockChangeHandler).toHaveBeenCalled();
});

it('should render null', async () => {
const mockChangeHandler = jest.fn().mockReturnValue(true);
const { queryAllByRole } = renderComponent([{ ...proceduralTypeCapabilities[0], actions: { view:'view-id' } }], mockChangeHandler);

expect(queryAllByRole('checkbox')).toHaveLength(0);
});
});
51 changes: 51 additions & 0 deletions lib/Capabilities/CapabilitiesSection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';

import { isEmpty } from 'lodash';
import CapabilitiesSettings from './CapabilitiesSettings';
import CapabilitiesProcedural from './CapabilitiesProcedural';
import CapabilitiesDataType from './CapabilitiesDataType';

/**
* Renders the CapabilitiesSection component.
*
* @param {Object} capabilities - the capabilities object with keys 'data', 'settings' and 'procedural'
* @param {boolean} readOnly - indicates if the component is read-only
* @param {Function} onChangeCapabilityCheckbox - the callback function for when capability checkbox is changed
* @param {Function} isCapabilitySelected - the callback function to check if capability is selected,
* @return {JSX.Element} - the rendered CapabilitiesSection component
*/

const CapabilitiesSection = ({ capabilities, readOnly, onChangeCapabilityCheckbox, isCapabilitySelected, isCapabilityDisabled }) => {
return <section>
{!isEmpty(capabilities.data) && <CapabilitiesDataType
isCapabilityDisabled={isCapabilityDisabled}
isCapabilitySelected={isCapabilitySelected}
onChangeCapabilityCheckbox={onChangeCapabilityCheckbox}
readOnly={readOnly}
content={capabilities.data}
/>}
{!isEmpty(capabilities.settings) && <CapabilitiesSettings
isCapabilityDisabled={isCapabilityDisabled}
isCapabilitySelected={isCapabilitySelected}
onChangeCapabilityCheckbox={onChangeCapabilityCheckbox}
readOnly={readOnly}
content={capabilities.settings}
/>}
{!isEmpty(capabilities.procedural) && <CapabilitiesProcedural
isCapabilityDisabled={isCapabilityDisabled}
isCapabilitySelected={isCapabilitySelected}
onChangeCapabilityCheckbox={onChangeCapabilityCheckbox}
readOnly={readOnly}
content={capabilities.procedural}
/>}
</section>;
};

CapabilitiesSection.propTypes = { capabilities: PropTypes.object.isRequired,
readOnly: PropTypes.bool,
onChangeCapabilityCheckbox: PropTypes.func,
isCapabilitySelected: PropTypes.func,
isCapabilityDisabled: PropTypes.func };

export default CapabilitiesSection;
Loading
Loading