From d6433040d3f2f5c883b8b4069093a5758c29706b Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Mon, 9 Dec 2024 17:07:24 +0100 Subject: [PATCH] Use organizations-storage settings api --- package.json | 23 ++-- .../NumberGeneratorSettings.js | 52 ++++++++ .../NumberGeneratorSettings.test.js | 117 ++++++++++++++++++ .../NumberGeneratorSettingsForm.js | 62 +++++++--- .../NumberGeneratorSettingsForm.test.js | 52 +++++--- src/Settings/NumberGeneratorSettings/index.js | 2 +- src/Settings/SettingsPage.js | 4 +- src/common/constants/api.js | 2 - src/common/constants/numberGenerator.js | 3 +- .../useVendorCodeGeneratorSettings.js | 13 +- .../useVendorCodeGeneratorSettings.test.js | 14 ++- translations/ui-organizations/en.json | 2 + 12 files changed, 281 insertions(+), 65 deletions(-) create mode 100644 src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.js create mode 100644 src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.test.js diff --git a/package.json b/package.json index 8cce3209..c0db48c3 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,14 @@ "organizations-storage.interfaces": "2.1", "organizations-storage.phone-numbers": "2.0", "organizations-storage.privileged-contacts": "1.0", - "organizations-storage.settings": "1.0", + "organizations-storage.settings": "1.1", "organizations-storage.urls": "1.1", - "settings": "1.1", "tags": "1.0", "users": "15.1 16.0" }, + "optionalOkapiInterfaces": { + "servint": "4.0" + }, "queryResource": "query", "icons": [ { @@ -66,7 +68,9 @@ { "permissionName": "ui-organizations.third-party-services.execute", "displayName": "Organizations: Permissions required to call services apart from mod-organizations-storage", - "replaces": ["ui-organizations.third-party-services"], + "replaces": [ + "ui-organizations.third-party-services" + ], "visible": false, "subPermissions": [ "acquisition.organization.events.get", @@ -87,8 +91,6 @@ "data-export.config.collection.get", "data-export.config.item.get", "erm.agreements.collection.get", - "mod-settings.entries.collection.get", - "mod-settings.global.read.ui-organizations.vendor-generator-setting", "orders.acquisition-methods.collection.get", "orders.acquisition-method.item.get", "organizations.organizations.collection.get", @@ -307,11 +309,9 @@ "displayName": "Settings (Organizations): Manage number generator options", "subPermissions": [ "settings.organizations.enabled", - "mod-settings.entries.collection.get", - "mod-settings.entries.item.post", - "mod-settings.entries.item.put", - "mod-settings.global.read.ui-organizations.vendor-generator-setting", - "mod-settings.global.write.ui-organizations.vendor-generator-setting" + "organizations-storage.settings.collection.get", + "organizations-storage.settings.item.post", + "organizations-storage.settings.item.put" ], "visible": true } @@ -399,8 +399,5 @@ "optionalDependencies": { "@folio/plugin-find-contact": "^5.0.0", "@folio/plugin-find-interface": "^5.0.0" - }, - "optionalOkapiInterfaces": { - "servint": "4.0" } } diff --git a/src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.js b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.js new file mode 100644 index 00000000..442278b8 --- /dev/null +++ b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.js @@ -0,0 +1,52 @@ +import { FormattedMessage } from 'react-intl'; + +import { Loading } from '@folio/stripes/components'; +import { useOkapiKy } from '@folio/stripes/core'; +import { useShowCallout } from '@folio/stripes-acq-components'; + +import NumberGeneratorSettingsForm from './NumberGeneratorSettingsForm'; +import { SETTINGS_API } from '../../common/constants/api'; +import { VENDOR_CODE_GENERATOR_SETTINGS_KEY } from '../../common/constants/numberGenerator'; +import { useVendorCodeGeneratorSettings } from '../../common/hooks/useVendorCodeGeneratorSettings'; + +const NumberGeneratorSettings = () => { + const { vendorCodeSetting, isLoading } = useVendorCodeGeneratorSettings(); + const ky = useOkapiKy(); + const sendCallout = useShowCallout(); + + const onSubmit = async ({ [VENDOR_CODE_GENERATOR_SETTINGS_KEY]: value }) => { + try { + if (vendorCodeSetting) { + await ky.put(`${SETTINGS_API}/${vendorCodeSetting.id}`, { + json: { ...vendorCodeSetting, value }, + }); + } else { + await ky.post(SETTINGS_API, { + json: { key: VENDOR_CODE_GENERATOR_SETTINGS_KEY, value }, + }); + } + + sendCallout({ + message: , + }); + } catch (error) { + sendCallout({ + type: 'error', + message: , + }); + } + }; + + if (isLoading) { + return ; + } + + return ( + + ); +}; + +export default NumberGeneratorSettings; diff --git a/src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.test.js b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.test.js new file mode 100644 index 00000000..1f5bc26f --- /dev/null +++ b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettings.test.js @@ -0,0 +1,117 @@ +import { FormattedMessage } from 'react-intl'; + +import { + render, + screen, +} from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { useOkapiKy } from '@folio/stripes/core'; +import { useShowCallout } from '@folio/stripes-acq-components'; + +import NumberGeneratorSettings from './NumberGeneratorSettings'; +import { SETTINGS_API } from '../../common/constants/api'; +import { useVendorCodeGeneratorSettings } from '../../common/hooks/useVendorCodeGeneratorSettings'; + +jest.mock('@folio/stripes/core', () => ({ + useOkapiKy: jest.fn(), +})); + +jest.mock('@folio/stripes-acq-components', () => ({ + useShowCallout: jest.fn(), +})); + +jest.mock('../../common/hooks/useVendorCodeGeneratorSettings'); + +jest.mock('@folio/stripes/components', () => ({ + Loading: () =>
Loading
, +})); + +jest.mock('../../common/constants/numberGenerator', () => ({ + ...jest.requireActual('../../common/constants/numberGenerator'), + VENDOR_CODE_GENERATOR_SETTINGS_KEY: 'testKey', +})); + +jest.mock('./NumberGeneratorSettingsForm', () => jest.fn(({ onSubmit }) => ( + +))); + +const renderComponent = () => render(); + +describe('NumberGeneratorSettings', () => { + const mockKyPut = jest.fn(); + const mockKyPost = jest.fn(); + const mockSendCallout = jest.fn(); + + beforeEach(() => { + useOkapiKy.mockReturnValue({ + put: mockKyPut, + post: mockKyPost, + }); + useShowCallout.mockReturnValue(mockSendCallout); + }); + + it('should render Loading', () => { + useVendorCodeGeneratorSettings.mockReturnValue({ isLoading: true }); + renderComponent(); + expect(screen.getByText('Loading')).toBeInTheDocument(); + }); + + it('should render NumberGeneratorSettingsForm', () => { + useVendorCodeGeneratorSettings.mockReturnValue({ isLoading: false }); + renderComponent(); + expect(screen.getByText('Submit')).toBeInTheDocument(); + }); + + it('should call ky.post when no setting is present', async () => { + useVendorCodeGeneratorSettings.mockReturnValue({ isLoading: false }); + renderComponent(); + + await userEvent.click(screen.getByText('Submit')); + + expect(mockKyPost).toHaveBeenCalledWith(SETTINGS_API, { + json: { key: 'testKey', value: 'testValue' }, + }); + expect(mockSendCallout).toHaveBeenCalledWith({ + message: , + }); + }); + + it('should call ky.put when setting exists', async () => { + const vendorCodeSetting = { id: '123', key: 'testKey', value: 'someValue', _version: 1 }; + + useVendorCodeGeneratorSettings.mockReturnValue({ + isLoading: false, + vendorCodeSetting, + }); + renderComponent(); + + await userEvent.click(screen.getByText('Submit')); + + expect(mockKyPut).toHaveBeenCalledWith(`${SETTINGS_API}/${vendorCodeSetting.id}`, { + json: { ...vendorCodeSetting, value: 'testValue' }, + }); + expect(mockSendCallout).toHaveBeenCalledWith({ + message: , + }); + }); + + it('should show error callout when submission fails', async () => { + useVendorCodeGeneratorSettings.mockReturnValue({ isLoading: false }); + mockKyPost.mockRejectedValue(new Error()); + renderComponent(); + + await userEvent.click(screen.getByText('Submit')); + + expect(mockSendCallout).toHaveBeenCalledWith({ + type: 'error', + message: , + }); + }); +}); diff --git a/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.js b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.js index 5edd4835..3f43e906 100644 --- a/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.js +++ b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.js @@ -1,40 +1,51 @@ -import { useMemo } from 'react'; +import PropTypes from 'prop-types'; import { Field } from 'react-final-form'; import { FormattedMessage } from 'react-intl'; import { + Button, Col, MessageBanner, + Pane, + PaneFooter, + PaneHeader, RadioButton, Row, } from '@folio/stripes/components'; -import { useStripes } from '@folio/stripes/core'; -import { ConfigManager } from '@folio/stripes/smart-components'; +import stripesFinalForm from '@folio/stripes/final-form'; import css from './NumberGeneratorSettingsForm.css'; import { VENDOR_CODE_GENERATOR_OPTIONS, VENDOR_CODE_GENERATOR_SETTINGS_KEY, - VENDOR_CODE_GENERATOR_SETTINGS_SCOPE, } from '../../common/constants/numberGenerator'; -export const NumberGeneratorSettingsForm = () => { - const stripes = useStripes(); - const ConnectedConfigManager = useMemo(() => stripes.connect(ConfigManager), [stripes]); +const NumberGeneratorSettingsForm = ({ handleSubmit, pristine, submitting }) => { + const paneHeader = (renderProps) => ( + } + /> + ); - const beforeSave = (data) => data[VENDOR_CODE_GENERATOR_SETTINGS_KEY] || ''; - const getInitialValues = (items) => ({ [VENDOR_CODE_GENERATOR_SETTINGS_KEY]: items?.[0]?.value || '' }); + const paneFooter = ( + + + + } + /> + ); return ( - } - onBeforeSave={beforeSave} - scope={VENDOR_CODE_GENERATOR_SETTINGS_SCOPE} - stripes={stripes} - > +
@@ -72,6 +83,19 @@ export const NumberGeneratorSettingsForm = () => { /> - + ); }; + +NumberGeneratorSettingsForm.propTypes = { + handleSubmit: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +export default stripesFinalForm({ + enableReinitialize: true, + keepDirtyOnReinitialize: true, + navigationCheck: true, + subscription: { values: true }, +})(NumberGeneratorSettingsForm); diff --git a/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js index b863f63a..81845d9d 100644 --- a/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js +++ b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js @@ -1,49 +1,63 @@ -import { Form } from 'react-final-form'; +import { MemoryRouter } from 'react-router-dom'; import { render, screen, } from '@folio/jest-config-stripes/testing-library/react'; -import { useStripes } from '@folio/stripes/core'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; -import { NumberGeneratorSettingsForm } from './NumberGeneratorSettingsForm'; +import NumberGeneratorSettingsForm from './NumberGeneratorSettingsForm'; +import { + VENDOR_CODE_GENERATOR_OPTIONS, + VENDOR_CODE_GENERATOR_SETTINGS_KEY, +} from '../../common/constants/numberGenerator'; jest.mock('@folio/stripes/core', () => ({ useStripes: jest.fn(), })); -jest.mock('@folio/stripes/smart-components', () => ({ - ConfigManager: jest.fn(({ children }) =>
{children}
), -})); - -const stripesMock = { - connect: jest.fn((component) => component), -}; +const onSubmitMock = jest.fn(); const renderComponent = () => render( -
onSubmitMock(values)} />, + { wrapper: MemoryRouter }, ); describe('NumberGeneratorSettingsForm', () => { - beforeEach(() => { - useStripes.mockReturnValue(stripesMock); - }); - - it('should render the component', () => { + it('should render the component with initial values', () => { renderComponent(); + expect(screen.getByText('ui-organizations.settings.numberGeneratorOptions')).toBeInTheDocument(); expect(screen.getByText('ui-organizations.settings.numberGeneratorOptions.info')).toBeInTheDocument(); expect( screen.getByLabelText('ui-organizations.settings.numberGeneratorOptions.useTextFieldForVendor'), ).toBeInTheDocument(); expect( screen.getByLabelText('ui-organizations.settings.numberGeneratorOptions.useBothForVendor'), - ).toBeInTheDocument(); + ).toBeChecked(); expect( screen.getByLabelText('ui-organizations.settings.numberGeneratorOptions.useGeneratorForVendor'), ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'stripes-core.button.save' })).toBeDisabled(); + }); + + it('should call onSubmit with correct values', async () => { + renderComponent(); + + const textfieldRadioButton = screen.getByRole('radio', { + name: 'ui-organizations.settings.numberGeneratorOptions.useTextFieldForVendor', + }); + const saveButton = screen.getByRole('button', { name: 'stripes-core.button.save' }); + + await userEvent.click(textfieldRadioButton); + expect(saveButton).toBeEnabled(); + + await userEvent.click(saveButton); + expect(onSubmitMock).toHaveBeenCalledWith({ + [VENDOR_CODE_GENERATOR_SETTINGS_KEY]: VENDOR_CODE_GENERATOR_OPTIONS.TEXTFIELD, + }); }); }); diff --git a/src/Settings/NumberGeneratorSettings/index.js b/src/Settings/NumberGeneratorSettings/index.js index af92cb0c..106b27f9 100644 --- a/src/Settings/NumberGeneratorSettings/index.js +++ b/src/Settings/NumberGeneratorSettings/index.js @@ -1 +1 @@ -export { NumberGeneratorSettingsForm } from './NumberGeneratorSettingsForm'; +export { default as NumberGeneratorSettings } from './NumberGeneratorSettings'; diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index b9903d9a..71efcd20 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -11,7 +11,7 @@ import { CategorySettings } from './CategorySettings'; import { TypeSettings } from './TypeSettings'; import { BankingAccountTypeSettings } from './BankingAccountTypeSettings'; import { BankingInformationSettings } from './BankingInformationSettings'; -import { NumberGeneratorSettingsForm } from './NumberGeneratorSettings'; +import { NumberGeneratorSettings } from './NumberGeneratorSettings'; const pages = [ { @@ -42,7 +42,7 @@ const bankingAccountTypesPage = { }; const numberGeneratorOptionsPage = { - component: NumberGeneratorSettingsForm, + component: NumberGeneratorSettings, label: , perm: 'ui-organizations.settings.numberGenerator.manage', route: 'numberGeneratorOptions', diff --git a/src/common/constants/api.js b/src/common/constants/api.js index 6ee846fb..75237d36 100644 --- a/src/common/constants/api.js +++ b/src/common/constants/api.js @@ -8,6 +8,4 @@ export const PRIVILEGED_CONTACTS_API = 'organizations-storage/privileged-contact export const SETTINGS_API = 'organizations-storage/settings'; export const TYPES_API = 'organizations-storage/organization-types'; -export const MOD_SETTINGS_API = 'settings/entries'; - export const MAX_LIMIT = 2147483647; diff --git a/src/common/constants/numberGenerator.js b/src/common/constants/numberGenerator.js index a18756f1..d18894b9 100644 --- a/src/common/constants/numberGenerator.js +++ b/src/common/constants/numberGenerator.js @@ -8,5 +8,4 @@ export const VENDOR_CODE_GENERATOR_OPTIONS = { }; export const VENDOR_CODE_GENERATOR_CODE = 'organizations_vendorCode'; -export const VENDOR_CODE_GENERATOR_SETTINGS_KEY = 'vendor-generator-setting'; -export const VENDOR_CODE_GENERATOR_SETTINGS_SCOPE = 'ui-organizations.vendor-generator-setting'; +export const VENDOR_CODE_GENERATOR_SETTINGS_KEY = 'vendor-code-generator-setting'; diff --git a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js index b77087c0..28b72c4c 100644 --- a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js +++ b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js @@ -6,13 +6,12 @@ import { useStripes, } from '@folio/stripes/core'; -import { MOD_SETTINGS_API } from '../../constants/api'; +import { SETTINGS_API } from '../../constants/api'; import { NUMBER_GENERATOR_INTERFACE_NAME, NUMBER_GENERATOR_INTERFACE_VERSION, VENDOR_CODE_GENERATOR_OPTIONS, VENDOR_CODE_GENERATOR_SETTINGS_KEY, - VENDOR_CODE_GENERATOR_SETTINGS_SCOPE, } from '../../constants/numberGenerator'; const { BOTH, TEXTFIELD, GENERATOR } = VENDOR_CODE_GENERATOR_OPTIONS; @@ -20,19 +19,21 @@ const { BOTH, TEXTFIELD, GENERATOR } = VENDOR_CODE_GENERATOR_OPTIONS; export const useVendorCodeGeneratorSettings = () => { const ky = useOkapiKy(); const stripes = useStripes(); - const enabled = !!stripes.hasInterface(NUMBER_GENERATOR_INTERFACE_NAME, NUMBER_GENERATOR_INTERFACE_VERSION); + const enabled = Boolean(stripes.hasInterface(NUMBER_GENERATOR_INTERFACE_NAME, NUMBER_GENERATOR_INTERFACE_VERSION)); const [namespace] = useNamespace({ key: VENDOR_CODE_GENERATOR_SETTINGS_KEY }); const searchParams = { - query: `scope==${VENDOR_CODE_GENERATOR_SETTINGS_SCOPE} and key==${VENDOR_CODE_GENERATOR_SETTINGS_KEY}`, + query: `key==${VENDOR_CODE_GENERATOR_SETTINGS_KEY}`, limit: 1, }; - const queryFn = () => ky.get(MOD_SETTINGS_API, { searchParams }).json(); + const queryFn = () => ky.get(SETTINGS_API, { searchParams }).json(); const { data, isFetching, isLoading } = useQuery([namespace], queryFn, { enabled }); - const settingValue = data?.items?.[0]?.value; + const vendorCodeSetting = data?.settings?.[0]; + const settingValue = vendorCodeSetting?.value; return { + vendorCodeSetting, isUseGenerator: settingValue === GENERATOR, isUseTextField: settingValue === TEXTFIELD, isUseBoth: settingValue === BOTH, diff --git a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js index 565f25cd..9ef21876 100644 --- a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js +++ b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js @@ -13,11 +13,21 @@ import { } from '@folio/stripes/core'; import { useVendorCodeGeneratorSettings } from './useVendorCodeGeneratorSettings'; +import { + VENDOR_CODE_GENERATOR_OPTIONS, + VENDOR_CODE_GENERATOR_SETTINGS_KEY, +} from '../../constants'; + +const settingsEntity = { + id: '3297a4ed-2071-4455-8874-23ff88029490', + key: VENDOR_CODE_GENERATOR_SETTINGS_KEY, + value: VENDOR_CODE_GENERATOR_OPTIONS.GENERATOR, +}; const mockKy = { get: jest.fn(() => ({ json: jest.fn(() => Promise.resolve({ - items: [{ value: 'useGenerator' }], + settings: [settingsEntity], })), })), }; @@ -42,6 +52,7 @@ describe('useVendorCodeGeneratorSettings', () => { await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(mockKy.get).toHaveBeenCalled(); + expect(result.current.vendorCodeSetting).toEqual(settingsEntity); expect(result.current.isUseGenerator).toBe(true); expect(result.current.isUseTextField).toBe(false); expect(result.current.isUseBoth).toBe(false); @@ -54,6 +65,7 @@ describe('useVendorCodeGeneratorSettings', () => { await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(mockKy.get).not.toHaveBeenCalled(); + expect(result.current.vendorCodeSetting).toBeUndefined(); expect(result.current.isUseGenerator).toBe(false); expect(result.current.isUseTextField).toBe(false); expect(result.current.isUseBoth).toBe(false); diff --git a/translations/ui-organizations/en.json b/translations/ui-organizations/en.json index 344ccf13..e2858593 100644 --- a/translations/ui-organizations/en.json +++ b/translations/ui-organizations/en.json @@ -503,6 +503,8 @@ "settings.numberGeneratorOptions.useGeneratorForVendor": "Number generator on, fixed: the vendor code can be filled using the generator only.", "settings.numberGeneratorOptions.useTextFieldForVendor": "Number generator off: the vendor code can be filled manually only.", "settings.numberGeneratorOptions.useBothForVendor": "Number generator on, editable: the vendor code can be filled using the generator and be edited, or filled manually.", + "settings.numberGeneratorOptions.save.error": "Error saving setting", + "settings.numberGeneratorOptions.save.success": "Setting was successfully saved", "numberGenerator.vendorCodeGenerator": "Vendor code generator", "numberGenerator.generateVendorCode": "Generate vendor code", "settings.bankingInformation": "Banking information",