From c304c617585bc8c64778765b23ea31d1575872b8 Mon Sep 17 00:00:00 2001 From: vashjs Date: Thu, 12 Dec 2024 00:07:27 +0100 Subject: [PATCH 1/8] UIBULKED-562 Include statistical code option on Instances bulk edit forms --- package.json | 4 +- .../ContentUpdatesForm/ValuesColumn.js | 10 ++++ .../InstanceStatisticalCodesControl.js | 48 +++++++++++++++++++ .../ContentUpdatesForm/helpers.js | 26 +++++++++- src/constants/core.js | 1 + src/constants/inAppActions.js | 15 ++++++ src/constants/selectOptions.js | 12 +++++ src/hooks/api/useStatisticalCodes.js | 47 ++++++++++++++++++ src/utils/helpers.js | 4 ++ translations/ui-bulk-edit/en.json | 4 ++ 10 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js create mode 100644 src/hooks/api/useStatisticalCodes.js diff --git a/package.json b/package.json index f7c37e7f..bddf384f 100644 --- a/package.json +++ b/package.json @@ -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" ] }, { diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js index b81c7b95..83525951 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js @@ -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 }) => { @@ -150,6 +151,14 @@ export const ValuesColumn = ({ action, allActions, actionIndex, onChange, option /> ); + const renderStatisticalCodesSelect = () => controlType === CONTROL_TYPES.STATISTICAL_CODES_SELECT && ( + + ); + return ( <> {renderTextField()} @@ -162,6 +171,7 @@ export const ValuesColumn = ({ action, allActions, actionIndex, onChange, option {renderNoteTypeSelect()} {renderNoteDuplicateTypeSelect()} {renderElectronicAccessRelationshipSelect()} + {renderStatisticalCodesSelect()} ); }; diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js new file mode 100644 index 00000000..4f7c6fa4 --- /dev/null +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js @@ -0,0 +1,48 @@ +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 = ({ actionValue, actionIndex, onChange }) => { + const { formatMessage } = useIntl(); + + const { statisticalCodes, isStatisticalCodesLoading } = useStatisticalCodes(); + const sortedStatisticalCodes = sortWithoutPlaceholder(statisticalCodes); + const title = getLabelByValue(sortedStatisticalCodes, actionValue); + + if (isStatisticalCodesLoading) return ; + + return ( +
+ { + 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} + /> +
+ ); +}; + +InstanceStatisticalCodesControl.propTypes = { + actionValue: PropTypes.arrayOf(PropTypes.object), + actionIndex: PropTypes.number, + onChange: PropTypes.func, +}; diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js index be700f78..c83e7645 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js @@ -20,7 +20,7 @@ import { noteActions, noteActionsWithMarc, noteActionsWithDuplicate, - electronicAccess, + electronicAccess, statisticalCodeActions, } from '../../../../../constants'; import { getActionParameters } from '../../../../../constants/actionParameters'; @@ -89,6 +89,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(); @@ -196,6 +197,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: '', @@ -425,6 +439,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))]; @@ -433,7 +449,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; diff --git a/src/constants/core.js b/src/constants/core.js index a9c05874..e57e0ffd 100644 --- a/src/constants/core.js +++ b/src/constants/core.js @@ -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 = { diff --git a/src/constants/inAppActions.js b/src/constants/inAppActions.js index cdb953c3..18d7c32b 100644 --- a/src/constants/inAppActions.js +++ b/src/constants/inAppActions.js @@ -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', @@ -119,6 +120,12 @@ export const getRemoveTheseAction = () => ({ disabled: false, }); +export const getRemoveSomeAction = () => ({ + value: ACTIONS.REMOVE_SOME, + label: , + disabled: false, +}); + export const getDuplicateToNoteAction = () => ({ value: ACTIONS.DUPLICATE, label: , @@ -142,6 +149,14 @@ export const suppressFromDiscActions = () => [ getSetToTrueAction(), getSetToFalseAction(), ]; + +export const statisticalCodeActions = () => [ + getPlaceholder(), + getAddAction(), + getRemoveSomeAction(), + getRemoveAllAction(), +]; + export const noteActions = () => [ getPlaceholder(), getAddToExistingAction(), diff --git a/src/constants/selectOptions.js b/src/constants/selectOptions.js index 772ea434..9af1c767 100644 --- a/src/constants/selectOptions.js +++ b/src/constants/selectOptions.js @@ -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', @@ -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 ]; @@ -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) => [ diff --git a/src/hooks/api/useStatisticalCodes.js b/src/hooks/api/useStatisticalCodes.js new file mode 100644 index 00000000..ee9dfba1 --- /dev/null +++ b/src/hooks/api/useStatisticalCodes.js @@ -0,0 +1,47 @@ +import { useQuery } from 'react-query'; + +import { useNamespace, useOkapiKy } from '@folio/stripes/core'; + +import { useErrorMessages } from '../useErrorMessages'; + + +export const STATISTICAL_CODES_KEY = 'STATISTICAL_CODES_KEY'; + +export const useStatisticalCodes = (options = {}) => { + const ky = useOkapiKy(); + const [namespaceKey] = useNamespace({ key: STATISTICAL_CODES_KEY }); + const { showErrorMessage } = useErrorMessages(); + + const sharedParams = { searchParams: { query: 'cql.allRecords=1', limit: 1000 } }; + + const { data: statisticalCodes, isLoading: isStatisticalCodesLoading } = useQuery( + { + queryKey: [namespaceKey], + cacheTime: Infinity, + staleTime: Infinity, + queryFn: () => Promise.all([ + ky.get('statistical-code-types', sharedParams).json() + .then(response => response.statisticalCodeTypes), + ky.get('statistical-codes', sharedParams).json() + .then(response => response.statisticalCodes), + ]), + select: ([statisticalCodeTypes, statisticalCodesArr]) => { + return statisticalCodesArr.map((statisticalCode) => { + const type = statisticalCodeTypes.find((codeType) => codeType.id === statisticalCode.statisticalCodeTypeId); + + return { + label: `${type.name}: ${statisticalCode.code} - ${statisticalCode.name}`, + value: statisticalCode.id, + }; + }); + }, + onError: showErrorMessage, + ...options, + }, + ); + + return { + statisticalCodes: statisticalCodes || [], + isStatisticalCodesLoading, + }; +}; diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 6f988857..59a56344 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -110,6 +110,10 @@ export const customFilter = (value, dataOptions) => { }, []); }; +export const customMultiSelectionFilter = (value, dataOptions) => { + return { renderedItems: customFilter(value, dataOptions) }; +}; + export const setIn = (obj, path, value) => { return setWith(clone(obj), path, value, clone); }; diff --git a/translations/ui-bulk-edit/en.json b/translations/ui-bulk-edit/en.json index a6fbfcd7..1d8c16bd 100644 --- a/translations/ui-bulk-edit/en.json +++ b/translations/ui-bulk-edit/en.json @@ -326,6 +326,7 @@ "columns.INSTANCE.Publication frequency": "Publication frequency", "columns.INSTANCE.Publication range": "Publication range", "columns.INSTANCE.Notes" : "Notes", + "columns.INSTANCE.Statistical code" : "Statistical code", "columns.logs.hrId": "ID", "columns.logs.operationType": "Bulk operation type", @@ -384,6 +385,7 @@ "layer.options.holdings.urlPublic": "URL public note", "layer.options.instances.staffSuppress": "Staff suppress", "layer.options.instances.suppress": "Suppress from discovery", + "layer.options.instances.statisticalCode": "Statistical code", "layer.options.expirationDate": "Expiration date", "layer.options.email": "Email", "layer.options.patronGroup": "Patron group", @@ -409,6 +411,7 @@ "layer.selectLocation": "Select location", "layer.selectPatronGroup": "Select patron group", "layer.selectLoanType": "Select loan type", + "layer.statisticalCode": "Select statistical code", "layer.selectNoteType": "Select note type", "layer.selectType": "Select note type", "layer.options.items.true": "Set true", @@ -517,6 +520,7 @@ "ariaLabel.location": "Location", "ariaLabel.statusSelect": "Status select", "ariaLabel.loanTypeSelect": "Loan type select", + "ariaLabel.statisticalCode": "Statistical code select", "ariaLabel.urlRelationshipSelect": "Url relationship select", "ariaLabel.columnFilter": "Column filter input", From 319a37f02cc195b442c8a8ddd091f35631e0f234 Mon Sep 17 00:00:00 2001 From: vashjs Date: Thu, 12 Dec 2024 00:08:28 +0100 Subject: [PATCH 2/8] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b94cd76..c0ab2c8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) From 8554eb1e41a09b6882067854e23430eed67dbab2 Mon Sep 17 00:00:00 2001 From: vashjs Date: Thu, 12 Dec 2024 00:13:49 +0100 Subject: [PATCH 3/8] refactor --- src/hooks/api/useStatisticalCodes.js | 15 ++++----------- src/utils/helpers.js | 11 +++++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/hooks/api/useStatisticalCodes.js b/src/hooks/api/useStatisticalCodes.js index ee9dfba1..394b95d2 100644 --- a/src/hooks/api/useStatisticalCodes.js +++ b/src/hooks/api/useStatisticalCodes.js @@ -3,6 +3,7 @@ import { useQuery } from 'react-query'; import { useNamespace, useOkapiKy } from '@folio/stripes/core'; import { useErrorMessages } from '../useErrorMessages'; +import { getMappedStatisticalCodes } from '../../utils/helpers'; export const STATISTICAL_CODES_KEY = 'STATISTICAL_CODES_KEY'; @@ -25,23 +26,15 @@ export const useStatisticalCodes = (options = {}) => { ky.get('statistical-codes', sharedParams).json() .then(response => response.statisticalCodes), ]), - select: ([statisticalCodeTypes, statisticalCodesArr]) => { - return statisticalCodesArr.map((statisticalCode) => { - const type = statisticalCodeTypes.find((codeType) => codeType.id === statisticalCode.statisticalCodeTypeId); - - return { - label: `${type.name}: ${statisticalCode.code} - ${statisticalCode.name}`, - value: statisticalCode.id, - }; - }); - }, + initialData: [], + select: getMappedStatisticalCodes, onError: showErrorMessage, ...options, }, ); return { - statisticalCodes: statisticalCodes || [], + statisticalCodes, isStatisticalCodesLoading, }; }; diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 59a56344..fc50bf25 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -180,3 +180,14 @@ export const getTransformedLogsFilterValue = (values) => { return Array.from(result); }; + +export const getMappedStatisticalCodes = ([statisticalCodeTypes, statisticalCodesArr]) => { + return statisticalCodesArr.map((statisticalCode) => { + const type = statisticalCodeTypes.find((codeType) => codeType.id === statisticalCode.statisticalCodeTypeId); + + return { + label: `${type.name}: ${statisticalCode.code} - ${statisticalCode.name}`, + value: statisticalCode.id, + }; + }); +}; From bca6fc2bafcdd9223e8c954e3898cae5feb38b81 Mon Sep 17 00:00:00 2001 From: vashjs Date: Thu, 12 Dec 2024 00:14:53 +0100 Subject: [PATCH 4/8] spacing --- .../BulkEditInApp/ContentUpdatesForm/helpers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js index c83e7645..9dd8a644 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.js @@ -20,7 +20,8 @@ import { noteActions, noteActionsWithMarc, noteActionsWithDuplicate, - electronicAccess, statisticalCodeActions, + electronicAccess, + statisticalCodeActions, } from '../../../../../constants'; import { getActionParameters } from '../../../../../constants/actionParameters'; From 036534c30e8416b06c4a4fbafa3e156ea37ee690 Mon Sep 17 00:00:00 2001 From: vashjs Date: Thu, 12 Dec 2024 01:15:21 +0100 Subject: [PATCH 5/8] update tests --- .../ContentUpdatesForm/ValuesColumn.js | 5 +- .../ContentUpdatesForm/ValuesColumn.test.js | 50 ++++++++++--- .../InstanceStatisticalCodesControl.js | 5 +- .../ContentUpdatesForm/helpers.test.js | 44 +++++++++++ src/hooks/api/index.js | 1 + src/hooks/api/useStatisticalCodes.js | 5 +- src/utils/helpers.test.js | 75 ++++++++++++++++++- 7 files changed, 166 insertions(+), 19 deletions(-) diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js index 83525951..e7399dbd 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.js @@ -153,9 +153,8 @@ export const ValuesColumn = ({ action, allActions, actionIndex, onChange, option const renderStatisticalCodesSelect = () => controlType === CONTROL_TYPES.STATISTICAL_CODES_SELECT && ( ); diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js index c0847241..d47cdd16 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js @@ -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'; @@ -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( @@ -54,11 +62,15 @@ describe('ValuesColumn Component', () => { isElectronicAccessLoading: false, electronicAccessRelationships: [], }); + + useStatisticalCodes.mockReturnValue({ + statisticalCodes: [], + isStatisticalCodesLoading: false, + }); }); afterEach(() => { - usePatronGroup.mockReset(); - useLoanTypes.mockReset(); + cleanup(); }); it('should render TextField when action type is INPUT', async () => { @@ -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); diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js index 4f7c6fa4..1810aebb 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js @@ -9,7 +9,7 @@ import { useStatisticalCodes } from '../../../../../../hooks/api/useStatisticalC import { customMultiSelectionFilter } from '../../../../../../utils/helpers'; -export const InstanceStatisticalCodesControl = ({ actionValue, actionIndex, onChange }) => { +export const InstanceStatisticalCodesControl = ({ actionName, actionValue, actionIndex, onChange }) => { const { formatMessage } = useIntl(); const { statisticalCodes, isStatisticalCodesLoading } = useStatisticalCodes(); @@ -21,7 +21,7 @@ export const InstanceStatisticalCodesControl = ({ actionValue, actionIndex, onCh return (
{ @@ -43,6 +43,7 @@ export const InstanceStatisticalCodesControl = ({ actionValue, actionIndex, onCh InstanceStatisticalCodesControl.propTypes = { actionValue: PropTypes.arrayOf(PropTypes.object), + actionName: PropTypes.string, actionIndex: PropTypes.number, onChange: PropTypes.func, }; diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.test.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.test.js index 277e8fd2..ce8fbf5b 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.test.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers.test.js @@ -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: , + disabled: true, + }, + { + value: ACTIONS.ADD_TO_EXISTING, + label: , + disabled: false, + }, + { + value: ACTIONS.REMOVE_SOME, + label: , + disabled: false, + }, + { + value: ACTIONS.REMOVE_ALL, + label: , + 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', diff --git a/src/hooks/api/index.js b/src/hooks/api/index.js index 81dda79f..cc770080 100644 --- a/src/hooks/api/index.js +++ b/src/hooks/api/index.js @@ -20,3 +20,4 @@ export * from './useHoldingsNotesEcs'; export * from './useLocationEcs'; export * from './useLoanTypesEcs'; export * from './useElectronicAccessEcs'; +export * from './useStatisticalCodes'; diff --git a/src/hooks/api/useStatisticalCodes.js b/src/hooks/api/useStatisticalCodes.js index 394b95d2..19e8cce5 100644 --- a/src/hooks/api/useStatisticalCodes.js +++ b/src/hooks/api/useStatisticalCodes.js @@ -15,7 +15,7 @@ export const useStatisticalCodes = (options = {}) => { const sharedParams = { searchParams: { query: 'cql.allRecords=1', limit: 1000 } }; - const { data: statisticalCodes, isLoading: isStatisticalCodesLoading } = useQuery( + const { data, isLoading: isStatisticalCodesLoading } = useQuery( { queryKey: [namespaceKey], cacheTime: Infinity, @@ -26,13 +26,14 @@ export const useStatisticalCodes = (options = {}) => { ky.get('statistical-codes', sharedParams).json() .then(response => response.statisticalCodes), ]), - initialData: [], select: getMappedStatisticalCodes, onError: showErrorMessage, ...options, }, ); + const statisticalCodes = data || []; + return { statisticalCodes, isStatisticalCodesLoading, diff --git a/src/utils/helpers.test.js b/src/utils/helpers.test.js index ec94aa94..7e62400e 100644 --- a/src/utils/helpers.test.js +++ b/src/utils/helpers.test.js @@ -1,5 +1,5 @@ import { - customFilter, + customFilter, getMappedStatisticalCodes, getTenantsById, getTransformedLogsFilterValue, removeDuplicatesByValue @@ -280,3 +280,76 @@ describe('getTransformedLogsFilterValue', () => { expect(result).toContain('other_value'); }); }); + +describe('getMappedStatisticalCodes', () => { + test('should correctly map statistical codes with corresponding types', () => { + const statisticalCodeTypes = [ + { id: '1', name: 'Type A' }, + { id: '2', name: 'Type B' }, + ]; + + const statisticalCodesArr = [ + { id: 'code1', statisticalCodeTypeId: '1', code: '001', name: 'Code One' }, + { id: 'code2', statisticalCodeTypeId: '2', code: '002', name: 'Code Two' }, + ]; + + const expected = [ + { label: 'Type A: 001 - Code One', value: 'code1' }, + { label: 'Type B: 002 - Code Two', value: 'code2' }, + ]; + + const result = getMappedStatisticalCodes([statisticalCodeTypes, statisticalCodesArr]); + expect(result).toEqual(expected); + }); + + test('should return an empty array when statisticalCodesArr is empty', () => { + const statisticalCodeTypes = [ + { id: '1', name: 'Type A' }, + { id: '2', name: 'Type B' }, + ]; + + const statisticalCodesArr = []; + + const expected = []; + + const result = getMappedStatisticalCodes([statisticalCodeTypes, statisticalCodesArr]); + expect(result).toEqual(expected); + }); + + test('should throw an error when a statistical code has no matching type', () => { + const statisticalCodeTypes = [ + { id: '1', name: 'Type A' }, + ]; + + const statisticalCodesArr = [ + { id: 'code1', statisticalCodeTypeId: '2', code: '002', name: 'Code Two' }, + ]; + + expect(() => { + getMappedStatisticalCodes([statisticalCodeTypes, statisticalCodesArr]); + }).toThrow(TypeError); + }); + + test('should correctly map multiple statistical codes', () => { + const statisticalCodeTypes = [ + { id: '1', name: 'Type A' }, + { id: '2', name: 'Type B' }, + { id: '3', name: 'Type C' }, + ]; + + const statisticalCodesArr = [ + { id: 'code1', statisticalCodeTypeId: '1', code: '001', name: 'Code One' }, + { id: 'code2', statisticalCodeTypeId: '2', code: '002', name: 'Code Two' }, + { id: 'code3', statisticalCodeTypeId: '3', code: '003', name: 'Code Three' }, + ]; + + const expected = [ + { label: 'Type A: 001 - Code One', value: 'code1' }, + { label: 'Type B: 002 - Code Two', value: 'code2' }, + { label: 'Type C: 003 - Code Three', value: 'code3' }, + ]; + + const result = getMappedStatisticalCodes([statisticalCodeTypes, statisticalCodesArr]); + expect(result).toEqual(expected); + }); +}); From 24837c71bbe3c02457b3f1918215e0b7e3f07fc9 Mon Sep 17 00:00:00 2001 From: vashjs Date: Thu, 12 Dec 2024 12:02:47 +0100 Subject: [PATCH 6/8] add tests for statical code --- src/hooks/api/useStatisticalCode.test.js | 98 ++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/hooks/api/useStatisticalCode.test.js diff --git a/src/hooks/api/useStatisticalCode.test.js b/src/hooks/api/useStatisticalCode.test.js new file mode 100644 index 00000000..a8539705 --- /dev/null +++ b/src/hooks/api/useStatisticalCode.test.js @@ -0,0 +1,98 @@ +import { QueryClient, QueryClientProvider } from 'react-query'; +import { renderHook } from '@testing-library/react-hooks'; + +import { useNamespace, useOkapiKy } from '@folio/stripes/core'; + +import { useErrorMessages } from '../useErrorMessages'; +import { useStatisticalCodes, STATISTICAL_CODES_KEY } from './useStatisticalCodes'; +import { getMappedStatisticalCodes } from '../../utils/helpers'; + + +jest.mock('@folio/stripes/core', () => ({ + useNamespace: jest.fn(), + useOkapiKy: jest.fn(), +})); + +jest.mock('../useErrorMessages', () => ({ + useErrorMessages: jest.fn(), +})); + +jest.mock('../../utils/helpers', () => ({ + getMappedStatisticalCodes: jest.fn(), +})); + +describe('useStatisticalCodes', () => { + const queryClient = new QueryClient(); + const wrapper = ({ children }) => ( + {children} + ); + + const mockNamespaceKey = `${STATISTICAL_CODES_KEY}_namespace`; + const mockKy = { + get: jest.fn(), + }; + const mockShowErrorMessage = jest.fn(); + const mockMappedStatisticalCodes = [{ id: '1', name: 'Code 1' }]; + + beforeEach(() => { + jest.clearAllMocks(); + + useNamespace.mockReturnValue([mockNamespaceKey]); + useOkapiKy.mockReturnValue(mockKy); + useErrorMessages.mockReturnValue({ showErrorMessage: mockShowErrorMessage }); + getMappedStatisticalCodes.mockReturnValue(mockMappedStatisticalCodes); + }); + + it('should fetch and return statistical codes data', async () => { + const statisticalCodeTypes = [{ id: 'type1', name: 'Type 1' }]; + const statisticalCodes = [{ id: 'code1', name: 'Code 1' }]; + + mockKy.get.mockImplementation((url) => { + if (url === 'statistical-code-types') { + return { + json: async () => ({ statisticalCodeTypes }), + }; + } + + if (url === 'statistical-codes') { + return { + json: async () => ({ statisticalCodes }), + }; + } + + throw new Error('Unexpected URL'); + }); + + const { result, waitFor } = renderHook(() => useStatisticalCodes(), { wrapper }); + + await waitFor(() => !result.current.isStatisticalCodesLoading); + + expect(result.current.statisticalCodes).toEqual(mockMappedStatisticalCodes); + expect(result.current.isStatisticalCodesLoading).toBe(false); + expect(mockKy.get).toHaveBeenCalledWith('statistical-code-types', { + searchParams: { query: 'cql.allRecords=1', limit: 1000 }, + }); + expect(mockKy.get).toHaveBeenCalledWith('statistical-codes', { + searchParams: { query: 'cql.allRecords=1', limit: 1000 }, + }); + expect(getMappedStatisticalCodes).toHaveBeenCalledWith([ + statisticalCodeTypes, + statisticalCodes, + ]); + }); + + it('should handle errors and call showErrorMessage', async () => { + const error = new Error('Failed to fetch'); + mockKy.get.mockRejectedValue(error); + + const { result, waitFor } = renderHook(() => useStatisticalCodes(), { wrapper }); + + await waitFor(() => !result.current.isStatisticalCodesLoading); + + expect(result.current.statisticalCodes).toEqual([{ + id: '1', + name: 'Code 1', + }]); + expect(result.current.isStatisticalCodesLoading).toBe(false); + }); +}); From 34bfb98f94f6b92524824e9668171582064a4a52 Mon Sep 17 00:00:00 2001 From: vashjs Date: Thu, 12 Dec 2024 16:59:43 +0100 Subject: [PATCH 7/8] align imports --- src/utils/helpers.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/helpers.test.js b/src/utils/helpers.test.js index 7e62400e..fbd91dd8 100644 --- a/src/utils/helpers.test.js +++ b/src/utils/helpers.test.js @@ -1,5 +1,6 @@ import { - customFilter, getMappedStatisticalCodes, + customFilter, + getMappedStatisticalCodes, getTenantsById, getTransformedLogsFilterValue, removeDuplicatesByValue From 84e046b5fff2af470e88fe95fbf8b674ce497db7 Mon Sep 17 00:00:00 2001 From: vashjs Date: Mon, 16 Dec 2024 13:55:14 +0100 Subject: [PATCH 8/8] fix comments --- .../ContentUpdatesForm/ValuesColumn.test.js | 6 +----- .../controls/InstanceStatisticalCodesControl.js | 16 +++++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js index d47cdd16..a5dc00cc 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/ValuesColumn.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { render, fireEvent, waitFor, cleanup } from '@testing-library/react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import '../../../../../../test/jest/__mock__/reactIntl.mock'; import { IntlProvider } from 'react-intl'; @@ -69,10 +69,6 @@ describe('ValuesColumn Component', () => { }); }); - afterEach(() => { - cleanup(); - }); - it('should render TextField when action type is INPUT', async () => { const { getByRole } = renderComponent(() => CONTROL_TYPES.INPUT); const element = getByRole('textbox'); diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js index 1810aebb..954e20f5 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInApp/ContentUpdatesForm/controls/InstanceStatisticalCodesControl.js @@ -16,6 +16,14 @@ export const InstanceStatisticalCodesControl = ({ actionName, actionValue, actio const sortedStatisticalCodes = sortWithoutPlaceholder(statisticalCodes); const title = getLabelByValue(sortedStatisticalCodes, actionValue); + const handleChange = value => { + onChange({ + actionIndex, + value, + fieldName: FIELD_VALUE_KEY, + }); + }; + if (isStatisticalCodesLoading) return ; return ( @@ -24,13 +32,7 @@ export const InstanceStatisticalCodesControl = ({ actionName, actionValue, actio key={actionName} id="statisticalCodes" value={actionValue} - onChange={value => { - onChange({ - actionIndex, - value, - fieldName: FIELD_VALUE_KEY, - }); - }} + onChange={handleChange} placeholder={formatMessage({ id: 'ui-bulk-edit.layer.statisticalCode' })} aria-label={formatMessage({ id: 'ui-bulk-edit.ariaLabel.statisticalCode' })} dataOptions={statisticalCodes}