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

UIBULKED-562 Include statistical code option on Instances bulk edit forms #663

Merged
merged 9 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* [UIBULKED-560](https://folio-org.atlassian.net/browse/UIBULKED-560) Update actions menu.
* [UIBULKED-585](https://folio-org.atlassian.net/browse/UUIBULKED-585) Adding missing translation and filters.
* [UIBULKED-561](https://folio-org.atlassian.net/browse/UUIBULKED-561) Add administrative data accordion to MARC bulk edit form.
* [UIBULKED-562](https://folio-org.atlassian.net/browse/UIBULKED-562) Include statistical code option on Instances bulk edit forms.

## [4.2.2](https://github.com/folio-org/ui-bulk-edit/tree/v4.2.2) (2024-11-15)

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@
"consortia.publications-results.item.get",
"consortium-search.institutions.collection.get",
"consortium-search.campuses.collection.get",
"consortium-search.libraries.collection.get"
"consortium-search.libraries.collection.get",
"inventory-storage.statistical-codes.collection.get",
"inventory-storage.statistical-code-types.collection.get"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { InstanceNotesControl } from './controls/InstanceNotesControl';
import { ElectronicAccessRelationshipControl } from './controls/ElectronicAccessRelationshipControl';
import { DuplicateNoteControl } from './controls/DuplicateNotesControl';
import { StatusControl } from './controls/StatusControl';
import { InstanceStatisticalCodesControl } from './controls/InstanceStatisticalCodesControl';


export const ValuesColumn = ({ action, allActions, actionIndex, onChange, option }) => {
Expand Down Expand Up @@ -150,6 +151,13 @@ export const ValuesColumn = ({ action, allActions, actionIndex, onChange, option
/>
);

const renderStatisticalCodesSelect = () => controlType === CONTROL_TYPES.STATISTICAL_CODES_SELECT && (
<InstanceStatisticalCodesControl
actionName={action.name}
{...sharedProps}
/>
);

return (
<>
{renderTextField()}
Expand All @@ -162,6 +170,7 @@ export const ValuesColumn = ({ action, allActions, actionIndex, onChange, option
{renderNoteTypeSelect()}
{renderNoteDuplicateTypeSelect()}
{renderElectronicAccessRelationshipSelect()}
{renderStatisticalCodesSelect()}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { render, fireEvent, waitFor, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import '../../../../../../test/jest/__mock__/reactIntl.mock';
import { IntlProvider } from 'react-intl';
Expand All @@ -9,25 +9,33 @@ import { createMemoryHistory } from 'history';
import { runAxeTest } from '@folio/stripes-testing';
import { queryClient } from '../../../../../../test/jest/utils/queryClient';
import { ValuesColumn } from './ValuesColumn';
import { useElectronicAccessRelationships, useLoanTypes, usePatronGroup } from '../../../../../hooks/api';
import {
useElectronicAccessRelationships,
useLoanTypes,
usePatronGroup,
useStatisticalCodes
} from '../../../../../hooks/api';
import { CAPABILITIES, CONTROL_TYPES } from '../../../../../constants';


jest.mock('../../../../../hooks/api/useLoanTypes');
jest.mock('../../../../../hooks/api/usePatronGroup');
jest.mock('../../../../../hooks/api/useElectronicAccess');
jest.mock('../../../../../hooks/api/useStatisticalCodes');

const onChange = jest.fn();

const history = createMemoryHistory();

const mockAction = {
type: '',
name: 'testName',
value: 'testValue',
};
const renderComponent = (actionType, override = {}) => {
const action = {
type: '',
name: 'testName',
value: 'testValue',
controlType: actionType,
...override,
};

const renderComponent = (actionType) => {
const action = { ...mockAction, controlType: actionType };
return render(
<Router history={history}>
<QueryClientProvider client={queryClient}>
Expand All @@ -54,11 +62,15 @@ describe('ValuesColumn Component', () => {
isElectronicAccessLoading: false,
electronicAccessRelationships: [],
});

useStatisticalCodes.mockReturnValue({
statisticalCodes: [],
isStatisticalCodesLoading: false,
});
});

afterEach(() => {
usePatronGroup.mockReset();
useLoanTypes.mockReset();
cleanup();
mariia-aloshyna marked this conversation as resolved.
Show resolved Hide resolved
});

it('should render TextField when action type is INPUT', async () => {
Expand Down Expand Up @@ -191,6 +203,22 @@ describe('ValuesColumn Component', () => {
await waitFor(() => expect(onChange).toHaveBeenCalled());
});

it('should render select with statistical codes when action type is ADD, or REMOVE_SOME', async () => {
const { getByRole } = renderComponent(
() => CONTROL_TYPES.STATISTICAL_CODES_SELECT,
{
value: [{ label: 'test', value: 'test' }],
}
);
const element = getByRole('combobox');

expect(element).toBeInTheDocument();

fireEvent.change(element, [{ label: 'test', value: 'test' }]);

await waitFor(() => expect(onChange).toHaveBeenCalled());
});

it('should render with no axe errors', async () => {
renderComponent(() => CONTROL_TYPES.ELECTRONIC_ACCESS_RELATIONSHIP_SELECT);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';

import { Loading, MultiSelection } from '@folio/stripes/components';

import { FIELD_VALUE_KEY, getLabelByValue, sortWithoutPlaceholder } from '../helpers';
import { useStatisticalCodes } from '../../../../../../hooks/api/useStatisticalCodes';
import { customMultiSelectionFilter } from '../../../../../../utils/helpers';


export const InstanceStatisticalCodesControl = ({ actionName, actionValue, actionIndex, onChange }) => {
const { formatMessage } = useIntl();

const { statisticalCodes, isStatisticalCodesLoading } = useStatisticalCodes();
const sortedStatisticalCodes = sortWithoutPlaceholder(statisticalCodes);
const title = getLabelByValue(sortedStatisticalCodes, actionValue);

if (isStatisticalCodesLoading) return <Loading size="large" />;

return (
<div title={title}>
<MultiSelection
key={actionName}
id="statisticalCodes"
value={actionValue}
onChange={value => {
mariia-aloshyna marked this conversation as resolved.
Show resolved Hide resolved
onChange({
actionIndex,
value,
fieldName: FIELD_VALUE_KEY,
});
}}
placeholder={formatMessage({ id: 'ui-bulk-edit.layer.statisticalCode' })}
aria-label={formatMessage({ id: 'ui-bulk-edit.ariaLabel.statisticalCode' })}
dataOptions={statisticalCodes}
dirty={!!actionValue}
filter={customMultiSelectionFilter}
/>
</div>
);
};

InstanceStatisticalCodesControl.propTypes = {
actionValue: PropTypes.arrayOf(PropTypes.object),
actionName: PropTypes.string,
actionIndex: PropTypes.number,
onChange: PropTypes.func,
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
noteActionsWithMarc,
noteActionsWithDuplicate,
electronicAccess,
statisticalCodeActions,
} from '../../../../../constants';
import { getActionParameters } from '../../../../../constants/actionParameters';

Expand Down Expand Up @@ -89,6 +90,7 @@ export const getDefaultActions = ({
const expirationDefaultActions = expirationActions();
const holdingsLocationDefaultActions = permanentHoldingsLocation();
const suppressDefaultActions = suppressFromDiscActions();
const statisticalCodeDefaultActions = statisticalCodeActions();
const statusDefaultActions = statusActions();
const loanDefaultActions = permanentLoanTypeActions();
const noteDefaultActions = noteActions();
Expand Down Expand Up @@ -196,6 +198,19 @@ export const getDefaultActions = ({
},
],
};
case OPTIONS.STATISTICAL_CODE:
return {
type: '',
actions: [
null,
{
actionsList: statisticalCodeDefaultActions,
controlType: () => CONTROL_TYPES.STATISTICAL_CODES_SELECT,
[ACTION_VALUE_KEY]: statisticalCodeDefaultActions[0].value,
[FIELD_VALUE_KEY]: '',
},
],
};
case OPTIONS.STAFF_SUPPRESS:
return {
type: '',
Expand Down Expand Up @@ -425,6 +440,8 @@ export const getLabelByValue = (items, targetValue) => {
};

export const sortWithoutPlaceholder = (array) => {
if (!array.length) return [];

const [placeholder, ...rest] = array;

return [placeholder, ...rest.sort((a, b) => a.label.localeCompare(b.label))];
Expand All @@ -433,7 +450,13 @@ export const sortWithoutPlaceholder = (array) => {
export const getMappedContentUpdates = (fields, options) => fields.map(({
parameters, tenants, option, actionsDetails: { actions }
}) => {
const [initial, updated] = actions.map(action => action?.value ?? null);
const [initial, updated] = actions.map(action => {
if (Array.isArray(action?.value)) {
return action.value.map(item => item?.value).join(',');
}

return action?.value || null;
});
const actionTenants = actions.map(action => action?.tenants);
const sourceOption = options.find(o => o.value === option);
const optionType = sourceOption?.type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,50 @@ describe('ContentUpdatesForm helpers', () => {
);
});

it('returns the correct object for the ELECTRONIC_ACCESS_MATERIALS_SPECIFIED option', () => {
expect(JSON.stringify(getDefaultActions({
option: OPTIONS.STATISTICAL_CODE,
options: [],
formatMessage,
capability: CAPABILITIES.INSTANCE
})))
.toEqual(
JSON.stringify({
type: '',
actions: [
null,
{
actionsList: [
{
value: '',
label: <FormattedMessage id="ui-bulk-edit.actions.placeholder" />,
disabled: true,
},
{
value: ACTIONS.ADD_TO_EXISTING,
label: <FormattedMessage id="ui-bulk-edit.layer.options.add" />,
disabled: false,
},
{
value: ACTIONS.REMOVE_SOME,
label: <FormattedMessage id="ui-bulk-edit.layer.options.items.removeNote" />,
disabled: false,
},
{
value: ACTIONS.REMOVE_ALL,
label: <FormattedMessage id="ui-bulk-edit.layer.options.items.removeAll" />,
disabled: false,
},
],
controlType: () => CONTROL_TYPES.STATISTICAL_CODES_SELECT,
[ACTION_VALUE_KEY]: '',
[FIELD_VALUE_KEY]: '',
},
],
}),
);
});

it('returns the correct object for the default case', () => {
expect(getDefaultActions({
option: 'unknown',
Expand Down
1 change: 1 addition & 0 deletions src/constants/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const CONTROL_TYPES = {
NOTE_SELECT: 'NOTE_SELECT',
NOTE_DUPLICATE_SELECT: 'NOTE_DUPLICATE_SELECT',
ELECTRONIC_ACCESS_RELATIONSHIP_SELECT: 'ELECTRONIC_ACCESS_RELATIONSHIP_SELECT',
STATISTICAL_CODES_SELECT: 'STATISTICAL_CODES_SELECT',
};

export const TRANSLATION_SUFFIX = {
Expand Down
15 changes: 15 additions & 0 deletions src/constants/inAppActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const ACTIONS = {
MARK_AS_STAFF_ONLY: 'MARK_AS_STAFF_ONLY',
REMOVE_MARK_AS_STAFF_ONLY: 'REMOVE_MARK_AS_STAFF_ONLY',
REMOVE_ALL: 'REMOVE_ALL',
REMOVE_SOME: 'REMOVE_SOME',
CHANGE_TYPE: 'CHANGE_TYPE',
DUPLICATE: 'DUPLICATE',

Expand Down Expand Up @@ -119,6 +120,12 @@ export const getRemoveTheseAction = () => ({
disabled: false,
});

export const getRemoveSomeAction = () => ({
value: ACTIONS.REMOVE_SOME,
label: <FormattedMessage id="ui-bulk-edit.layer.options.items.removeNote" />,
disabled: false,
});

export const getDuplicateToNoteAction = () => ({
value: ACTIONS.DUPLICATE,
label: <FormattedMessage id="ui-bulk-edit.layer.options.items.duplicateTo" />,
Expand All @@ -142,6 +149,14 @@ export const suppressFromDiscActions = () => [
getSetToTrueAction(),
getSetToFalseAction(),
];

export const statisticalCodeActions = () => [
getPlaceholder(),
getAddAction(),
getRemoveSomeAction(),
getRemoveAllAction(),
];

export const noteActions = () => [
getPlaceholder(),
getAddToExistingAction(),
Expand Down
12 changes: 12 additions & 0 deletions src/constants/selectOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const OPTIONS = {
TEMPORARY_LOCATION: 'TEMPORARY_LOCATION',
PERMANENT_LOCATION: 'PERMANENT_LOCATION',
SUPPRESS_FROM_DISCOVERY: 'SUPPRESS_FROM_DISCOVERY',
STATISTICAL_CODE: 'STATISTICAL_CODE',
STAFF_SUPPRESS: 'STAFF_SUPPRESS',
STATUS: 'STATUS',
EXPIRATION_DATE: 'EXPIRATION_DATE',
Expand Down Expand Up @@ -287,6 +288,12 @@ export const getInstanceOptions = (formatMessage, instanceNotes) => [
disabled: false,
categoryName: formatMessage({ id: 'ui-bulk-edit.category.administrativeData' }),
},
{
value: OPTIONS.STATISTICAL_CODE,
label: formatMessage({ id: 'ui-bulk-edit.layer.options.instances.statisticalCode' }),
disabled: false,
categoryName: formatMessage({ id: 'ui-bulk-edit.category.administrativeData' }),
},
...instanceNotes
];

Expand All @@ -306,6 +313,11 @@ export const getAdministrativeDataOptions = (formatMessage) => [
label: formatMessage({ id: 'ui-bulk-edit.layer.options.instances.suppress' }),
disabled: false,
},
{
value: OPTIONS.STATISTICAL_CODE,
label: formatMessage({ id: 'ui-bulk-edit.layer.options.instances.statisticalCode' }),
disabled: false,
},
];

export const getHoldingsNotes = (formatMessage, holdingsNotes) => [
Expand Down
1 change: 1 addition & 0 deletions src/hooks/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from './useHoldingsNotesEcs';
export * from './useLocationEcs';
export * from './useLoanTypesEcs';
export * from './useElectronicAccessEcs';
export * from './useStatisticalCodes';
Loading
Loading