From 556ce31a7ae8f8b56e985b7f8b4fbcb545313d6e Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Thu, 15 Sep 2022 14:41:40 +0100 Subject: [PATCH 01/23] feat: Number Generator Hooked up Number generator to Organizations --- package.json | 2 + .../FieldCode/FieldCode.js | 47 ++++++++--- src/Settings/NumberGeneratorOptions.js | 83 +++++++++++++++++++ src/Settings/SettingsPage.js | 7 ++ src/common/constants/api.js | 2 + src/common/hooks/index.js | 1 + src/common/hooks/useSettings/index.js | 1 + src/common/hooks/useSettings/useSettings.js | 61 ++++++++++++++ translations/ui-organizations/en.json | 4 + 9 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 src/Settings/NumberGeneratorOptions.js create mode 100644 src/common/hooks/useSettings/index.js create mode 100644 src/common/hooks/useSettings/useSettings.js diff --git a/package.json b/package.json index 5a827c60..fa3abc37 100644 --- a/package.json +++ b/package.json @@ -246,6 +246,7 @@ "@bigtest/mocha": "^0.5.2", "@bigtest/react": "^0.1.2", "@folio/eslint-config-stripes": "^6.1.0", + "@folio/service-interaction": "^1.0.0", "@folio/stripes": "^7.0.0", "@folio/stripes-cli": "^2.4.0", "@formatjs/cli": "^4.2.16", @@ -292,6 +293,7 @@ "redux-form": "^8.3.0" }, "peerDependencies": { + "@folio/service-interaction": "^1.0.0", "@folio/stripes": "^7.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js index 201e31e1..c8c89c87 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js @@ -1,13 +1,17 @@ import React, { useCallback } from 'react'; -import { Field } from 'react-final-form'; +import { Field, useForm } from 'react-final-form'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import { stripesConnect } from '@folio/stripes/core'; -import { TextField } from '@folio/stripes/components'; +import { Col, Row, TextField } from '@folio/stripes/components'; +import { NumberGeneratorButton } from '@folio/service-interaction'; import { fetchOrgsByParam } from '../../../../common/resources'; import { validateOrgCode } from './validateOrgCode'; +import { useSettings } from '../../../../common/hooks'; + +const CONFIG_NAME = 'number_generator'; const FieldCode = ({ orgId, mutator }) => { const validate = useCallback(value => { @@ -16,15 +20,38 @@ const FieldCode = ({ orgId, mutator }) => { // eslint-disable-next-line react-hooks/exhaustive-deps [orgId]); + const { change } = useForm(); + + const { settings } = useSettings([CONFIG_NAME]); + const vendorCodeSetting = settings?.find(sett => sett?.configName === CONFIG_NAME)?.parsedSettings?.vendorGeneratorSetting ?? 'useTextField'; + return ( - } - name="code" - required - validate={validate} - /> + + + } + name="code" + required + validate={validate} + /> + + {( + vendorCodeSetting === 'useGenerator' || + vendorCodeSetting === 'useBoth' + ) && + + change('code', generated)} + id="vendor-code-generator" + generator="Vendor" + sequence="vendor" + /> + + } + ); }; diff --git a/src/Settings/NumberGeneratorOptions.js b/src/Settings/NumberGeneratorOptions.js new file mode 100644 index 00000000..21c85ec0 --- /dev/null +++ b/src/Settings/NumberGeneratorOptions.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { Field } from 'react-final-form'; + +import { stripesConnect, withStripes } from '@folio/stripes/core'; +import { ConfigManager } from '@folio/stripes/smart-components'; + +import { Col, RadioButton, Row } from '@folio/stripes/components'; + +const NumberGeneratorOptions = (props) => { + const ConnectedConfigManager = stripesConnect(ConfigManager); + + const defaultValues = { + useGenerator: 'useBoth', + }; + + const beforeSave = (data) => { + return JSON.stringify(data); + }; + + const getInitialValues = (settings) => { + let loadedValues = {}; + + try { + const value = settings.length === 0 ? '' : settings[0].value; + + loadedValues = JSON.parse(value); + } catch (e) { } // eslint-disable-line no-empty + + return { + ...defaultValues, + ...loadedValues, + }; + }; + + return ( + } + moduleName="ORGANIZATIONS" + onBeforeSave={beforeSave} + stripes={props.stripes} + formType="final-form" + > + + + } + type="radio" + value="useGenerator" + /> + } + type="radio" + value="useTextField" + /> + } + type="radio" + value="useBoth" + /> + + + + ); +}; + +NumberGeneratorOptions.propTypes = { + stripes: PropTypes.object, +}; + +export default withStripes(NumberGeneratorOptions); diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index d3737b1a..effebaf6 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -5,6 +5,7 @@ import { Settings } from '@folio/stripes/smart-components'; import CategorySettings from './CategorySettings'; import { TypeSettings } from './TypeSettings'; +import NumberGeneratorOptions from './NumberGeneratorOptions'; const pages = [ { @@ -19,6 +20,12 @@ const pages = [ perm: 'settings.organizations.enabled', route: 'type', }, + { + component: NumberGeneratorOptions, + label: , + perm: 'settings.organizations.enabled', + route: 'numberGeneratorOptions', + }, ]; const SettingsPage = (props) => ( diff --git a/src/common/constants/api.js b/src/common/constants/api.js index e4d3413e..e05c34bc 100644 --- a/src/common/constants/api.js +++ b/src/common/constants/api.js @@ -4,4 +4,6 @@ export const INTERFACES_API = 'organizations-storage/interfaces'; export const TYPES_API = 'organizations-storage/organization-types'; export const AGREEMENTS_API = 'erm/sas'; +export const CONFIG_API = 'configurations/entries'; + export const MAX_LIMIT = 2147483647; diff --git a/src/common/hooks/index.js b/src/common/hooks/index.js index 393e24a9..f74bbf4c 100644 --- a/src/common/hooks/index.js +++ b/src/common/hooks/index.js @@ -4,3 +4,4 @@ export * from './useIntegrationConfigMutation'; export * from './useLinkedAgreements'; export * from './useTranslatedCategories'; export * from './useTypes'; +export * from './useSettings'; diff --git a/src/common/hooks/useSettings/index.js b/src/common/hooks/useSettings/index.js new file mode 100644 index 00000000..3deefacf --- /dev/null +++ b/src/common/hooks/useSettings/index.js @@ -0,0 +1 @@ +export { useSettings } from './useSettings'; diff --git a/src/common/hooks/useSettings/useSettings.js b/src/common/hooks/useSettings/useSettings.js new file mode 100644 index 00000000..7cc05169 --- /dev/null +++ b/src/common/hooks/useSettings/useSettings.js @@ -0,0 +1,61 @@ +import { useQuery } from 'react-query'; + +import { + useNamespace, + useOkapiKy, +} from '@folio/stripes/core'; + +import { CONFIG_API } from '../../constants'; + +export const useSettings = (configNames = []) => { + const ky = useOkapiKy(); + const [namespace] = useNamespace({ key: 'settings' }); + + let query = 'module==ORGANIZATIONS'; + + if (configNames?.length > 0) { + const configNamesString = configNames?.map(cfn => `configName==${cfn}`).join(' or '); + + query = `${query} AND (${configNamesString})`; + } + + const searchParams = { + query, + }; + + const queryKeys = [ + namespace, + ]; + + const queryFn = () => ky.get(CONFIG_API, { searchParams }).json(); + const options = { + enabled: configNames?.length > 0, + keepPreviousData: true, + }; + + const { + data, + isFetching, + isLoading, + ...rest + } = useQuery( + queryKeys, + queryFn, + options, + ); + + const settings = data?.configs ?? []; + + const parsedSettings = settings?.map(sett => ({ + ...sett, + parsedSettings: JSON.parse(sett?.value ?? '{}'), + })); + + return ({ + settings: parsedSettings, + isFetching, + isLoading, + totalCount: data?.totalRecords, + ...rest, + }); +}; diff --git a/translations/ui-organizations/en.json b/translations/ui-organizations/en.json index 8aef4c6a..d4568a6d 100644 --- a/translations/ui-organizations/en.json +++ b/translations/ui-organizations/en.json @@ -428,6 +428,10 @@ "settings.type": "Type", "settings.typeStatus.Active": "Active", "settings.typeStatus.Inactive": "Inactive", + "settings.numberGeneratorOptions": "Number generator options", + "settings.numberGeneratorOptions.useGeneratorForVendor": "Use number generator for vendor code.", + "settings.numberGeneratorOptions.useTextFieldForVendor": "Use text field for vendor code.", + "settings.numberGeneratorOptions.useBothForVendor": "Use number generator for vendor code but allow editing via text field.", "permission.view": "Organizations: View", "permission.edit": "Organizations: View, edit", "permission.create": "Organizations: View, edit, create", From 7a04e05b49b0a0e9a6f14741a294d93055bced00 Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 16 Sep 2022 10:42:21 +0100 Subject: [PATCH 02/23] chore: Interfaces Added servint as an optionalOkapiInterface dependency, and protected code against being called when interface is not present --- package.json | 3 ++ .../FieldCode/FieldCode.js | 9 +++- src/Settings/SettingsPage.js | 45 +++++++++++++------ 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index fa3abc37..d267a0b4 100644 --- a/package.json +++ b/package.json @@ -309,5 +309,8 @@ "optionalDependencies": { "@folio/plugin-find-contact": "^3.2.0", "@folio/plugin-find-interface": "^3.2.0" + }, + "optionalOkapiInterfaces": { + "servint": "2.0" } } diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js index c8c89c87..910f6700 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js @@ -3,7 +3,7 @@ import { Field, useForm } from 'react-final-form'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -import { stripesConnect } from '@folio/stripes/core'; +import { stripesConnect, useStripes } from '@folio/stripes/core'; import { Col, Row, TextField } from '@folio/stripes/components'; import { NumberGeneratorButton } from '@folio/service-interaction'; @@ -21,9 +21,14 @@ const FieldCode = ({ orgId, mutator }) => { [orgId]); const { change } = useForm(); + const stripes = useStripes(); const { settings } = useSettings([CONFIG_NAME]); - const vendorCodeSetting = settings?.find(sett => sett?.configName === CONFIG_NAME)?.parsedSettings?.vendorGeneratorSetting ?? 'useTextField'; + let vendorCodeSetting = 'useTextField'; + + if (stripes.hasInterface('servint')) { + vendorCodeSetting = settings?.find(sett => sett?.configName === CONFIG_NAME)?.parsedSettings?.vendorGeneratorSetting ?? 'useTextField'; + } return ( diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index effebaf6..70f2bfc7 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { Settings } from '@folio/stripes/smart-components'; @@ -20,20 +21,38 @@ const pages = [ perm: 'settings.organizations.enabled', route: 'type', }, - { - component: NumberGeneratorOptions, - label: , - perm: 'settings.organizations.enabled', - route: 'numberGeneratorOptions', - }, ]; -const SettingsPage = (props) => ( - } - /> -); +const SettingsPage = (props) => { + const { stripes } = props; + + if (stripes.hasInterface('servint')) { + pages.push({ + component: NumberGeneratorOptions, + label: , + perm: 'settings.organizations.enabled', + route: 'numberGeneratorOptions', + },) + } + + return ( + } + /> + ); +}; + +SettingsPage.propTypes = { + stripes: PropTypes.shape({ + hasInterface: PropTypes.func.isRequired, + timezone: PropTypes.string.isRequired, + store: PropTypes.shape({ + dispatch: PropTypes.func.isRequired, + getState: PropTypes.func, + }), + }).isRequired, +} export default SettingsPage; From b3d112e03d01086b81ecc3d7cce51c3f7fd2530a Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 3 Feb 2023 10:13:52 +0000 Subject: [PATCH 03/23] chore: Tweaks Small tweaks, brought number generator code in line with users PR refs UIORGS-336, UIORGS-337 --- src/Settings/NumberGeneratorOptions.js | 5 ++++- src/Settings/SettingsPage.js | 20 ++++++++------------ translations/ui-organizations/en.json | 6 +++--- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Settings/NumberGeneratorOptions.js b/src/Settings/NumberGeneratorOptions.js index 21c85ec0..ab67580a 100644 --- a/src/Settings/NumberGeneratorOptions.js +++ b/src/Settings/NumberGeneratorOptions.js @@ -26,7 +26,10 @@ const NumberGeneratorOptions = (props) => { const value = settings.length === 0 ? '' : settings[0].value; loadedValues = JSON.parse(value); - } catch (e) { } // eslint-disable-line no-empty + } catch (e) { + // Make sure we return _something_ because ConfigManager no longer has a safety check here + return {}; + } return { ...defaultValues, diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index 70f2bfc7..68e4834d 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -21,20 +21,16 @@ const pages = [ perm: 'settings.organizations.enabled', route: 'type', }, + { + component: NumberGeneratorOptions, + label: , + perm: 'settings.organizations.enabled', + interface: 'servint', + route: 'numberGeneratorOptions', + }, ]; const SettingsPage = (props) => { - const { stripes } = props; - - if (stripes.hasInterface('servint')) { - pages.push({ - component: NumberGeneratorOptions, - label: , - perm: 'settings.organizations.enabled', - route: 'numberGeneratorOptions', - },) - } - return ( Date: Thu, 16 Feb 2023 16:33:35 +0000 Subject: [PATCH 04/23] fix: Generator code Update generator code to use the new codes defined in this PR: https://github.com/folio-org/mod-service-interaction/pull/72 --- .../OrganizationSummaryForm/FieldCode/FieldCode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js index 910f6700..72a04dfc 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js @@ -51,7 +51,7 @@ const FieldCode = ({ orgId, mutator }) => { change('code', generated)} id="vendor-code-generator" - generator="Vendor" + generator="organizations_vendorCode" sequence="vendor" /> From 0017207f0d01c5c11842e0a40668684d56097251 Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Thu, 16 Feb 2023 16:43:16 +0000 Subject: [PATCH 05/23] feat: Permission Added new permission and blocked access to number generator page based on that permission --- package.json | 8 ++++++++ src/Settings/SettingsPage.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6790eb4a..d4d48a27 100644 --- a/package.json +++ b/package.json @@ -215,6 +215,14 @@ "organizations-storage.categories.item.post", "organizations-storage.categories.item.put" ] + }, + { + "permissionName": "ui-organizations.settings.numberGenerator.manage", + "displayName": "Settings (Organizations): Manage number generator options", + "subPermissions": [ + "settings.organizations.enabled" + ], + "visible": true } ] }, diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index 68e4834d..d1eb47e9 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -24,7 +24,7 @@ const pages = [ { component: NumberGeneratorOptions, label: , - perm: 'settings.organizations.enabled', + perm: 'ui-organizations.settings.numberGenerator.manage', interface: 'servint', route: 'numberGeneratorOptions', }, From f9a299d8a6c495e48de7868e16ed4c182ca3613a Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Tue, 20 Jun 2023 09:26:59 +0100 Subject: [PATCH 06/23] build: OptionalOkapiInterfaces Update to use servint 3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fde1b00..902c7197 100644 --- a/package.json +++ b/package.json @@ -314,7 +314,7 @@ "@folio/plugin-find-interface": "^3.2.0" }, "optionalOkapiInterfaces": { - "servint": "2.0", + "servint": "2.0 3.0", "@folio/plugin-find-contact": "^4.0.0", "@folio/plugin-find-interface": "^4.0.0" } From ce20f2305b1cc709c31ced5c4acbfa5d65f980f6 Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Mon, 7 Aug 2023 17:44:06 +0100 Subject: [PATCH 07/23] build: New service interaction Use new service-interaction --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 902c7197..b02a217f 100644 --- a/package.json +++ b/package.json @@ -252,7 +252,7 @@ "@bigtest/mocha": "^0.5.2", "@bigtest/react": "^0.1.2", "@folio/eslint-config-stripes": "^6.1.0", - "@folio/service-interaction": "^1.0.0", + "@folio/service-interaction": "^2.0.0", "@folio/stripes": "^8.0.0", "@folio/stripes-cli": "^2.4.0", "@formatjs/cli": "^4.2.16", @@ -299,7 +299,7 @@ "redux-form": "^8.3.0" }, "peerDependencies": { - "@folio/service-interaction": "^1.0.0", + "@folio/service-interaction": "^2.0.0", "@folio/stripes": "^8.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", From 5366269e97933cad5f3255ec9ceba9318e992c1d Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 8 Sep 2023 14:53:56 +0100 Subject: [PATCH 08/23] feat: Number generator Swapped implementation to full Modal implementation as per the other apps --- .../FieldCode/FieldCode.js | 11 ++++++++--- src/Settings/NumberGeneratorOptions.js | 16 ++++++++-------- translations/ui-organizations/en.json | 2 ++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js index 72a04dfc..e5a8aaef 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { stripesConnect, useStripes } from '@folio/stripes/core'; import { Col, Row, TextField } from '@folio/stripes/components'; -import { NumberGeneratorButton } from '@folio/service-interaction'; +import { NumberGeneratorModalButton } from '@folio/service-interaction'; import { fetchOrgsByParam } from '../../../../common/resources'; import { validateOrgCode } from './validateOrgCode'; @@ -48,11 +48,16 @@ const FieldCode = ({ orgId, mutator }) => { vendorCodeSetting === 'useBoth' ) && - } callback={(generated) => change('code', generated)} + fullWidth id="vendor-code-generator" + generateButtonLabel={} generator="organizations_vendorCode" - sequence="vendor" + modalProps={{ + label: + }} /> } diff --git a/src/Settings/NumberGeneratorOptions.js b/src/Settings/NumberGeneratorOptions.js index ab67580a..e49a4cb9 100644 --- a/src/Settings/NumberGeneratorOptions.js +++ b/src/Settings/NumberGeneratorOptions.js @@ -49,14 +49,6 @@ const NumberGeneratorOptions = (props) => { > - } - type="radio" - value="useGenerator" - /> { type="radio" value="useBoth" /> + } + type="radio" + value="useGenerator" + /> diff --git a/translations/ui-organizations/en.json b/translations/ui-organizations/en.json index 43a37277..c424da00 100644 --- a/translations/ui-organizations/en.json +++ b/translations/ui-organizations/en.json @@ -443,6 +443,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.", + "numberGenerator.vendorCodeGenerator": "Vendor code generator", + "numberGenerator.generateVendorCode": "Generate vendor code", "permission.view": "Organizations: View", "permission.edit": "Organizations: View, edit", "permission.create": "Organizations: View, edit, create", From fbaabdf7a1196cf8168ea82417cbf84e0bfc658d Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 29 Sep 2023 12:43:10 +0100 Subject: [PATCH 09/23] chore: Button UX Changed Button from fullWidth to scale with text --- .../OrganizationSummaryForm/FieldCode/FieldCode.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js index e5a8aaef..960f46df 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js @@ -51,7 +51,6 @@ const FieldCode = ({ orgId, mutator }) => { } callback={(generated) => change('code', generated)} - fullWidth id="vendor-code-generator" generateButtonLabel={} generator="organizations_vendorCode" From d618b90dba271a3380bc55d89ccfd9b4933d2bcd Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 8 Dec 2023 11:09:03 +0000 Subject: [PATCH 10/23] chore: Revert to state on master Change back to do what is done on master --- src/Settings/SettingsPage.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index a37b76de..24ac0117 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -39,14 +39,15 @@ const pages = [ }, ]; +const bankingAccountTypesPage = { + component: BankingAccountTypeSettings, + label: , + perm: 'settings.organizations.enabled', + route: 'banking-account-types', +}; + const SettingsPage = (props) => { const { enabled } = useBankingInformationSettings(); - const bankingAccountTypesPage = { - component: BankingAccountTypeSettings, - label: , - perm: 'settings.organizations.enabled', - route: 'banking-account-types', - }; const settingsPages = useMemo(() => (enabled ? pages.concat(bankingAccountTypesPage) : pages), [enabled]); From 5724de30741cb25c569c411034d5d6a84fc51ee4 Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 1 Mar 2024 11:49:38 +0000 Subject: [PATCH 11/23] test: mock useSettings Mocked useSettings to rectify failing test --- .../OrganizationSummaryForm.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js index 15e5b133..f48ad93d 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js @@ -11,6 +11,15 @@ import { useTypes } from '../../../common/hooks'; jest.mock('../../../common/hooks', () => ({ useTypes: jest.fn(), + useSettings: jest.fn(() => ([ + { + id: 'f2b84177-d85a-4b0c-93dd-279e8f9d1d42', + module: 'ORGANIZATIONS', + configName: 'number_generator', + enabled: true, + value: '{"vendorGeneratorSetting":"useBoth"}', + }, + ])), })); const TestForm = stripesFinalForm({})( From e0f9cff5befb2e7cf0d850d22887375b213e926b Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 1 Mar 2024 11:53:44 +0000 Subject: [PATCH 12/23] test: Updated jest snapshot --- .../OrganizationSummaryForm.test.js.snap | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap b/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap index 1e46016a..a2bd9669 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap @@ -52,42 +52,50 @@ exports[`OrganizationSummaryForm should render correct structure 1`] = ` class="col-xs-6 col-md-3" >
-
- +
+ +
+ +
+
+
-
Date: Fri, 1 Mar 2024 12:00:47 +0000 Subject: [PATCH 13/23] test: Test fails on CI because of non mocked component -- easy fix --- .../OrganizationSummaryForm/OrganizationSummaryForm.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js index f48ad93d..d727575d 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js @@ -22,6 +22,10 @@ jest.mock('../../../common/hooks', () => ({ ])), })); +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + const TestForm = stripesFinalForm({})( () => { return ( From 79e4c34c13d2648a05cd5ae34d0e4f8bf44e9ed8 Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Thu, 28 Mar 2024 10:38:40 +0000 Subject: [PATCH 14/23] build: Change service interaction dependency to ^3.0.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6b0f273b..25b15d4f 100644 --- a/package.json +++ b/package.json @@ -331,7 +331,7 @@ "@bigtest/react": "^0.1.2", "@folio/eslint-config-stripes": "^7.0.0", "@folio/jest-config-stripes": "^2.0.0", - "@folio/service-interaction": "^2.0.0", + "@folio/service-interaction": "^3.0.0", "@folio/stripes": "^9.0.0", "@folio/stripes-cli": "^3.0.0", "@formatjs/cli": "^6.1.3", @@ -371,7 +371,7 @@ "redux-form": "^8.3.0" }, "peerDependencies": { - "@folio/service-interaction": "^2.0.0", + "@folio/service-interaction": "^3.0.0", "@folio/stripes": "^9.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", From 5e4e4c2166f500b44b80451c27faf8e8b6a25c7f Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Fri, 12 Apr 2024 13:32:13 +0100 Subject: [PATCH 15/23] refactor: Refactor tweaks Moved connected component outside of component render, removed default values --- src/Settings/NumberGeneratorOptions.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Settings/NumberGeneratorOptions.js b/src/Settings/NumberGeneratorOptions.js index e49a4cb9..019c6603 100644 --- a/src/Settings/NumberGeneratorOptions.js +++ b/src/Settings/NumberGeneratorOptions.js @@ -3,18 +3,17 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { Field } from 'react-final-form'; -import { stripesConnect, withStripes } from '@folio/stripes/core'; +import { + stripesConnect, + withStripes, +} from '@folio/stripes/core'; import { ConfigManager } from '@folio/stripes/smart-components'; import { Col, RadioButton, Row } from '@folio/stripes/components'; -const NumberGeneratorOptions = (props) => { - const ConnectedConfigManager = stripesConnect(ConfigManager); - - const defaultValues = { - useGenerator: 'useBoth', - }; +const ConnectedConfigManager = stripesConnect(ConfigManager); +const NumberGeneratorOptions = (props) => { const beforeSave = (data) => { return JSON.stringify(data); }; @@ -32,7 +31,6 @@ const NumberGeneratorOptions = (props) => { } return { - ...defaultValues, ...loadedValues, }; }; @@ -41,11 +39,11 @@ const NumberGeneratorOptions = (props) => { } moduleName="ORGANIZATIONS" onBeforeSave={beforeSave} stripes={props.stripes} - formType="final-form" > From bae809dcd8c2f5889f2560da8e143c918c3fcad4 Mon Sep 17 00:00:00 2001 From: Ethan Freestone Date: Tue, 7 May 2024 13:01:19 +0100 Subject: [PATCH 16/23] chore: MessageBanner Added messageBanner to settings page explaining the number generator options --- src/Settings/NumberGeneratorOptions.css | 5 +++++ src/Settings/NumberGeneratorOptions.js | 13 ++++++++++++- translations/ui-organizations/en.json | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/Settings/NumberGeneratorOptions.css diff --git a/src/Settings/NumberGeneratorOptions.css b/src/Settings/NumberGeneratorOptions.css new file mode 100644 index 00000000..78ddf841 --- /dev/null +++ b/src/Settings/NumberGeneratorOptions.css @@ -0,0 +1,5 @@ +@import '@folio/stripes-components/lib/variables'; + +.marginBottomGutter { + margin-bottom: var(--gutter); +} diff --git a/src/Settings/NumberGeneratorOptions.js b/src/Settings/NumberGeneratorOptions.js index 019c6603..77ceb929 100644 --- a/src/Settings/NumberGeneratorOptions.js +++ b/src/Settings/NumberGeneratorOptions.js @@ -9,7 +9,9 @@ import { } from '@folio/stripes/core'; import { ConfigManager } from '@folio/stripes/smart-components'; -import { Col, RadioButton, Row } from '@folio/stripes/components'; +import { Col, MessageBanner, RadioButton, Row } from '@folio/stripes/components'; + +import css from './NumberGeneratorOptions.css'; const ConnectedConfigManager = stripesConnect(ConfigManager); @@ -45,6 +47,15 @@ const NumberGeneratorOptions = (props) => { onBeforeSave={beforeSave} stripes={props.stripes} > + + +
+ + + +
+ +
Date: Mon, 18 Nov 2024 09:36:28 +0100 Subject: [PATCH 17/23] Update dependencies --- package.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2ed44de9..d84d2d3e 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ } ], "stripesDeps": [ + "@folio/service-interaction", "@folio/stripes-acq-components" ], "permissionSets": [ @@ -333,9 +334,10 @@ "@bigtest/react": "^0.1.2", "@folio/eslint-config-stripes": "^7.0.0", "@folio/jest-config-stripes": "^2.0.0", - "@folio/service-interaction": "^3.0.0", + "@folio/service-interaction": "^3.1.0", "@folio/stripes": "^9.0.0", "@folio/stripes-cli": "^3.0.0", + "@folio/stripes-erm-components": "^9.2.0", "@formatjs/cli": "^6.1.3", "babel-jest": "^26.3.0", "chai": "^4.2.0", @@ -374,7 +376,7 @@ "redux-form": "^8.3.0" }, "peerDependencies": { - "@folio/service-interaction": "^3.0.0", + "@folio/service-interaction": "^3.1.0", "@folio/stripes": "^9.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -389,6 +391,6 @@ "@folio/plugin-find-interface": "^5.0.0" }, "optionalOkapiInterfaces": { - "servint": "2.0 3.0" + "servint": "4.0" } } From 30ab1587a7fa59dae07dcdccbc49431dac4c3678 Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Mon, 18 Nov 2024 17:06:05 +0100 Subject: [PATCH 18/23] Update implementation --- package.json | 10 ++- .../FieldCode/FieldCode.js | 51 ++++++------ .../OrganizationSummaryForm.test.js | 67 ++++++++++++--- .../OrganizationSummaryForm.test.js.snap | 72 ++++++++-------- .../NumberGeneratorSettingsForm.css} | 0 .../NumberGeneratorSettingsForm.js} | 82 ++++++++----------- .../NumberGeneratorSettingsForm.test.js | 49 +++++++++++ src/Settings/NumberGeneratorSettings/index.js | 1 + src/Settings/SettingsPage.js | 46 +++++------ src/Settings/SettingsPage.test.js | 18 +++- src/common/constants/api.js | 2 +- src/common/constants/index.js | 1 + src/common/constants/numberGenerator.js | 12 +++ src/common/hooks/index.js | 2 +- src/common/hooks/useSettings/index.js | 1 - src/common/hooks/useSettings/useSettings.js | 61 -------------- .../useVendorCodeGeneratorSettings/index.js | 1 + .../useVendorCodeGeneratorSettings.js | 43 ++++++++++ .../useVendorCodeGeneratorSettings.test.js | 62 ++++++++++++++ 19 files changed, 365 insertions(+), 216 deletions(-) rename src/Settings/{NumberGeneratorOptions.css => NumberGeneratorSettings/NumberGeneratorSettingsForm.css} (100%) rename src/Settings/{NumberGeneratorOptions.js => NumberGeneratorSettings/NumberGeneratorSettingsForm.js} (50%) create mode 100644 src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js create mode 100644 src/Settings/NumberGeneratorSettings/index.js create mode 100644 src/common/constants/numberGenerator.js delete mode 100644 src/common/hooks/useSettings/index.js delete mode 100644 src/common/hooks/useSettings/useSettings.js create mode 100644 src/common/hooks/useVendorCodeGeneratorSettings/index.js create mode 100644 src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js create mode 100644 src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js diff --git a/package.json b/package.json index d84d2d3e..89d47e85 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "organizations-storage.privileged-contacts": "1.0", "organizations-storage.settings": "1.0", "organizations-storage.urls": "1.1", + "settings": "1.1", "tags": "1.0", "users": "15.1 16.0" }, @@ -84,6 +85,8 @@ "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", @@ -301,7 +304,12 @@ "permissionName": "ui-organizations.settings.numberGenerator.manage", "displayName": "Settings (Organizations): Manage number generator options", "subPermissions": [ - "settings.organizations.enabled" + "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" ], "visible": true } diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js index 960f46df..e3c1fc1c 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js @@ -1,41 +1,45 @@ import React, { useCallback } from 'react'; -import { Field, useForm } from 'react-final-form'; +import { + Field, + useForm, +} from 'react-final-form'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -import { stripesConnect, useStripes } from '@folio/stripes/core'; -import { Col, Row, TextField } from '@folio/stripes/components'; import { NumberGeneratorModalButton } from '@folio/service-interaction'; +import { stripesConnect } from '@folio/stripes/core'; +import { + Col, + Row, + TextField, +} from '@folio/stripes/components'; +import { VENDOR_CODE_GENERATOR_CODE } from '../../../../common/constants'; +import { useVendorCodeGeneratorSettings } from '../../../../common/hooks'; import { fetchOrgsByParam } from '../../../../common/resources'; import { validateOrgCode } from './validateOrgCode'; -import { useSettings } from '../../../../common/hooks'; - -const CONFIG_NAME = 'number_generator'; const FieldCode = ({ orgId, mutator }) => { + const { change, resetFieldState } = useForm(); + const { isUseGenerator, isUseBoth } = useVendorCodeGeneratorSettings(); + const validate = useCallback(value => { return validateOrgCode(mutator.fetchOrgByCode, orgId, value); }, // eslint-disable-next-line react-hooks/exhaustive-deps [orgId]); - const { change } = useForm(); - const stripes = useStripes(); - - const { settings } = useSettings([CONFIG_NAME]); - let vendorCodeSetting = 'useTextField'; - - if (stripes.hasInterface('servint')) { - vendorCodeSetting = settings?.find(sett => sett?.configName === CONFIG_NAME)?.parsedSettings?.vendorGeneratorSetting ?? 'useTextField'; - } + const handleGeneratedValue = useCallback((generatedValue) => { + change('code', generatedValue); + resetFieldState('code'); + }, [change, resetFieldState]); return ( } name="code" @@ -43,23 +47,20 @@ const FieldCode = ({ orgId, mutator }) => { validate={validate} /> - {( - vendorCodeSetting === 'useGenerator' || - vendorCodeSetting === 'useBoth' - ) && + {(isUseGenerator || isUseBoth) && ( } - callback={(generated) => change('code', generated)} - id="vendor-code-generator" + callback={handleGeneratedValue} generateButtonLabel={} - generator="organizations_vendorCode" + generator={VENDOR_CODE_GENERATOR_CODE} + id="vendor-code-generator" modalProps={{ - label: + label: , }} /> - } + )} ); }; diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js index 0f94a4b2..32843e0e 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js @@ -1,4 +1,5 @@ import React from 'react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import { MemoryRouter } from 'react-router-dom'; @@ -8,23 +9,25 @@ import { organization, organizationTypes } from 'fixtures'; import { QueryClient, QueryClientProvider } from 'react-query'; import OrganizationSummaryForm from './OrganizationSummaryForm'; -import { useTypes } from '../../../common/hooks'; +import { + useTypes, + useVendorCodeGeneratorSettings, +} from '../../../common/hooks'; jest.mock('../../../common/hooks', () => ({ useTypes: jest.fn(), - useSettings: jest.fn(() => ([ - { - id: 'f2b84177-d85a-4b0c-93dd-279e8f9d1d42', - module: 'ORGANIZATIONS', - configName: 'number_generator', - enabled: true, - value: '{"vendorGeneratorSetting":"useBoth"}', - }, - ])), + useVendorCodeGeneratorSettings: jest.fn(), })); jest.mock('@folio/service-interaction', () => ({ - NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+ NumberGeneratorModalButton: ({ callback }) => ( + + ), })); const TestForm = stripesFinalForm({})( @@ -57,6 +60,11 @@ describe('OrganizationSummaryForm', () => { useTypes .mockClear() .mockReturnValue({ organizationTypes, totalRecords: organizationTypes.length }); + useVendorCodeGeneratorSettings.mockReturnValue({ + isUseGenerator: false, + isUseBoth: false, + isUseTextfield: false, + }); }); afterEach(() => { @@ -86,4 +94,41 @@ describe('OrganizationSummaryForm', () => { expect((selectedType).length).toEqual(1); }); + + it('should not render the NumberGeneratorModalButton', async () => { + renderForm(); + + expect(screen.getByRole('textbox', { name: 'ui-organizations.summary.code' })).toBeEnabled(); + expect(screen.queryByRole('button', { name: 'NumberGeneratorModalButton' })).not.toBeInTheDocument(); + }); + + it('should render the NumberGeneratorModalButton when setting=isUseGenerator', async () => { + useVendorCodeGeneratorSettings.mockReturnValue({ isUseGenerator: true }); + + renderForm(); + + expect(screen.getByRole('textbox', { name: 'ui-organizations.summary.code' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'NumberGeneratorModalButton' })).toBeInTheDocument(); + }); + + it('should render the NumberGeneratorModalButton when setting=isUseBoth', async () => { + useVendorCodeGeneratorSettings.mockReturnValue({ isUseBoth: true }); + + renderForm(); + + expect(screen.getByRole('textbox', { name: 'ui-organizations.summary.code' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'NumberGeneratorModalButton' })).toBeInTheDocument(); + }); + + it('should update vendor code field value when NumberGeneratorModalButton is clicked', async () => { + useVendorCodeGeneratorSettings.mockReturnValue({ isUseBoth: true }); + + renderForm(); + const button = screen.getByRole('button', { name: 'NumberGeneratorModalButton' }); + const input = screen.getByRole('textbox', { name: 'ui-organizations.summary.code' }); + + expect(input).toHaveValue(''); + await userEvent.click(button); + expect(input).toHaveValue('abc123'); + }); }); diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap b/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap index 2cc61f34..8b925fc6 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap @@ -52,50 +52,42 @@ exports[`OrganizationSummaryForm should render correct structure 1`] = ` class="col-xs-6 col-md-3" >
-
-
{ - const beforeSave = (data) => { - return JSON.stringify(data); - }; - - const getInitialValues = (settings) => { - let loadedValues = {}; - - try { - const value = settings.length === 0 ? '' : settings[0].value; +import css from './NumberGeneratorSettingsForm.css'; +import { + VENDOR_CODE_GENERATOR_OPTIONS, + VENDOR_CODE_GENERATOR_SETTINGS_KEY, + VENDOR_CODE_GENERATOR_SETTINGS_SCOPE, +} from '../../common/constants/numberGenerator'; - loadedValues = JSON.parse(value); - } catch (e) { - // Make sure we return _something_ because ConfigManager no longer has a safety check here - return {}; - } +export const NumberGeneratorSettingsForm = () => { + const stripes = useStripes(); + const ConnectedConfigManager = useMemo(() => stripes.connect(ConfigManager), [stripes]); - return { - ...loadedValues, - }; - }; + const beforeSave = (data) => data[VENDOR_CODE_GENERATOR_SETTINGS_KEY] || ''; + const getInitialValues = (items) => ({ [VENDOR_CODE_GENERATOR_SETTINGS_KEY]: items?.[0]?.value || '' }); return ( } - moduleName="ORGANIZATIONS" onBeforeSave={beforeSave} - stripes={props.stripes} + scope={VENDOR_CODE_GENERATOR_SETTINGS_SCOPE} + stripes={stripes} > @@ -60,36 +48,30 @@ const NumberGeneratorOptions = (props) => { } + name={VENDOR_CODE_GENERATOR_SETTINGS_KEY} type="radio" - value="useTextField" + value={VENDOR_CODE_GENERATOR_OPTIONS.TEXTFIELD} /> } + name={VENDOR_CODE_GENERATOR_SETTINGS_KEY} type="radio" - value="useBoth" + value={VENDOR_CODE_GENERATOR_OPTIONS.BOTH} /> } + name={VENDOR_CODE_GENERATOR_SETTINGS_KEY} type="radio" - value="useGenerator" + value={VENDOR_CODE_GENERATOR_OPTIONS.GENERATOR} /> ); }; - -NumberGeneratorOptions.propTypes = { - stripes: PropTypes.object, -}; - -export default withStripes(NumberGeneratorOptions); diff --git a/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js new file mode 100644 index 00000000..b863f63a --- /dev/null +++ b/src/Settings/NumberGeneratorSettings/NumberGeneratorSettingsForm.test.js @@ -0,0 +1,49 @@ +import { Form } from 'react-final-form'; + +import { + render, + screen, +} from '@folio/jest-config-stripes/testing-library/react'; +import { useStripes } from '@folio/stripes/core'; + +import { NumberGeneratorSettingsForm } from './NumberGeneratorSettingsForm'; + +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 renderComponent = () => render( +
, +); + +describe('NumberGeneratorSettingsForm', () => { + beforeEach(() => { + useStripes.mockReturnValue(stripesMock); + }); + + it('should render the component', () => { + renderComponent(); + + 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(); + expect( + screen.getByLabelText('ui-organizations.settings.numberGeneratorOptions.useGeneratorForVendor'), + ).toBeInTheDocument(); + }); +}); diff --git a/src/Settings/NumberGeneratorSettings/index.js b/src/Settings/NumberGeneratorSettings/index.js new file mode 100644 index 00000000..af92cb0c --- /dev/null +++ b/src/Settings/NumberGeneratorSettings/index.js @@ -0,0 +1 @@ +export { NumberGeneratorSettingsForm } from './NumberGeneratorSettingsForm'; diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index f684a91d..b9903d9a 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -1,15 +1,17 @@ -import React, { useMemo } from 'react'; -import PropTypes from 'prop-types'; +import { useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import { Settings } from '@folio/stripes/smart-components'; -import { useBankingInformationSettings } from '../common/hooks'; +import { + useBankingInformationSettings, + useVendorCodeGeneratorSettings, +} from '../common/hooks'; import { CategorySettings } from './CategorySettings'; import { TypeSettings } from './TypeSettings'; -import NumberGeneratorOptions from './NumberGeneratorOptions'; import { BankingAccountTypeSettings } from './BankingAccountTypeSettings'; import { BankingInformationSettings } from './BankingInformationSettings'; +import { NumberGeneratorSettingsForm } from './NumberGeneratorSettings'; const pages = [ { @@ -24,13 +26,6 @@ const pages = [ perm: 'ui-organizations.settings.view', route: 'type', }, - { - component: NumberGeneratorOptions, - label: , - perm: 'ui-organizations.settings.numberGenerator.manage', - interface: 'servint', - route: 'numberGeneratorOptions', - }, { component: BankingInformationSettings, label: , @@ -46,10 +41,24 @@ const bankingAccountTypesPage = { route: 'banking-account-types', }; +const numberGeneratorOptionsPage = { + component: NumberGeneratorSettingsForm, + label: , + perm: 'ui-organizations.settings.numberGenerator.manage', + route: 'numberGeneratorOptions', +}; + const SettingsPage = (props) => { - const { enabled } = useBankingInformationSettings(); + const { enabled: bankingInformationEnabled } = useBankingInformationSettings(); + const { enabled: numberGeneratorEnabled } = useVendorCodeGeneratorSettings(); - const settingsPages = useMemo(() => (enabled ? pages.concat(bankingAccountTypesPage) : pages), [enabled]); + const settingsPages = useMemo(() => { + return [ + ...pages, + ...(bankingInformationEnabled ? [bankingAccountTypesPage] : []), + ...(numberGeneratorEnabled ? [numberGeneratorOptionsPage] : []), + ]; + }, [bankingInformationEnabled, numberGeneratorEnabled]); return ( { ); }; -SettingsPage.propTypes = { - stripes: PropTypes.shape({ - hasInterface: PropTypes.func.isRequired, - timezone: PropTypes.string.isRequired, - store: PropTypes.shape({ - dispatch: PropTypes.func.isRequired, - getState: PropTypes.func, - }), - }).isRequired, -}; - export default SettingsPage; diff --git a/src/Settings/SettingsPage.test.js b/src/Settings/SettingsPage.test.js index 2ba0c333..8ce16c67 100644 --- a/src/Settings/SettingsPage.test.js +++ b/src/Settings/SettingsPage.test.js @@ -2,7 +2,10 @@ import { MemoryRouter } from 'react-router-dom'; import { screen, render } from '@folio/jest-config-stripes/testing-library/react'; -import { useBankingInformationSettings } from '../common/hooks'; +import { + useBankingInformationSettings, + useVendorCodeGeneratorSettings, +} from '../common/hooks'; import SettingsPage from './SettingsPage'; jest.mock('@folio/stripes/core'); @@ -18,6 +21,9 @@ jest.mock('../common/hooks', () => ({ isLoading: false, enabled: false, })), + useVendorCodeGeneratorSettings: jest.fn(() => ({ + enabled: false, + })), })); const stripesMock = { @@ -49,6 +55,8 @@ describe('SettingsPage', () => { expect(screen.getByText('ui-organizations.settings.categories')).toBeInTheDocument(); expect(screen.getByText('ui-organizations.settings.types')).toBeInTheDocument(); expect(screen.getByText('ui-organizations.settings.bankingInformation')).toBeInTheDocument(); + expect(screen.queryByText('ui-organizations.settings.bankingAccountTypes')).not.toBeInTheDocument(); + expect(screen.queryByText('ui-organizations.settings.numberGeneratorOptions')).not.toBeInTheDocument(); }); it('should return banking account types link', async () => { @@ -61,4 +69,12 @@ describe('SettingsPage', () => { expect(screen.getByText('ui-organizations.settings.bankingAccountTypes')).toBeInTheDocument(); }); + + it('should return numberGeneratorOptions link', async () => { + useVendorCodeGeneratorSettings.mockReturnValueOnce({ enabled: true }); + + renderSettingsPage(); + + expect(screen.getByText('ui-organizations.settings.numberGeneratorOptions')).toBeInTheDocument(); + }); }); diff --git a/src/common/constants/api.js b/src/common/constants/api.js index 5d3d53ee..6ee846fb 100644 --- a/src/common/constants/api.js +++ b/src/common/constants/api.js @@ -8,6 +8,6 @@ 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 CONFIG_API = 'configurations/entries'; +export const MOD_SETTINGS_API = 'settings/entries'; export const MAX_LIMIT = 2147483647; diff --git a/src/common/constants/index.js b/src/common/constants/index.js index 52453f15..36d718ed 100644 --- a/src/common/constants/index.js +++ b/src/common/constants/index.js @@ -2,6 +2,7 @@ export * from './api'; export * from './categories'; export * from './events'; export * from './interfaces'; +export * from './numberGenerator'; export * from './organization'; export * from './organizationTypes'; export * from './routes'; diff --git a/src/common/constants/numberGenerator.js b/src/common/constants/numberGenerator.js new file mode 100644 index 00000000..a18756f1 --- /dev/null +++ b/src/common/constants/numberGenerator.js @@ -0,0 +1,12 @@ +export const NUMBER_GENERATOR_INTERFACE_NAME = 'servint'; +export const NUMBER_GENERATOR_INTERFACE_VERSION = '4.0'; + +export const VENDOR_CODE_GENERATOR_OPTIONS = { + BOTH: 'useBoth', + TEXTFIELD: 'useTextField', + GENERATOR: 'useGenerator', +}; + +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'; diff --git a/src/common/hooks/index.js b/src/common/hooks/index.js index 45d16b23..c22e5b3b 100644 --- a/src/common/hooks/index.js +++ b/src/common/hooks/index.js @@ -10,4 +10,4 @@ export * from './useLinkedAgreements'; export * from './useOrganizationBankingInformation'; export * from './useTranslatedCategories'; export * from './useTypes'; -export * from './useSettings'; +export * from './useVendorCodeGeneratorSettings'; diff --git a/src/common/hooks/useSettings/index.js b/src/common/hooks/useSettings/index.js deleted file mode 100644 index 3deefacf..00000000 --- a/src/common/hooks/useSettings/index.js +++ /dev/null @@ -1 +0,0 @@ -export { useSettings } from './useSettings'; diff --git a/src/common/hooks/useSettings/useSettings.js b/src/common/hooks/useSettings/useSettings.js deleted file mode 100644 index 7cc05169..00000000 --- a/src/common/hooks/useSettings/useSettings.js +++ /dev/null @@ -1,61 +0,0 @@ -import { useQuery } from 'react-query'; - -import { - useNamespace, - useOkapiKy, -} from '@folio/stripes/core'; - -import { CONFIG_API } from '../../constants'; - -export const useSettings = (configNames = []) => { - const ky = useOkapiKy(); - const [namespace] = useNamespace({ key: 'settings' }); - - let query = 'module==ORGANIZATIONS'; - - if (configNames?.length > 0) { - const configNamesString = configNames?.map(cfn => `configName==${cfn}`).join(' or '); - - query = `${query} AND (${configNamesString})`; - } - - const searchParams = { - query, - }; - - const queryKeys = [ - namespace, - ]; - - const queryFn = () => ky.get(CONFIG_API, { searchParams }).json(); - const options = { - enabled: configNames?.length > 0, - keepPreviousData: true, - }; - - const { - data, - isFetching, - isLoading, - ...rest - } = useQuery( - queryKeys, - queryFn, - options, - ); - - const settings = data?.configs ?? []; - - const parsedSettings = settings?.map(sett => ({ - ...sett, - parsedSettings: JSON.parse(sett?.value ?? '{}'), - })); - - return ({ - settings: parsedSettings, - isFetching, - isLoading, - totalCount: data?.totalRecords, - ...rest, - }); -}; diff --git a/src/common/hooks/useVendorCodeGeneratorSettings/index.js b/src/common/hooks/useVendorCodeGeneratorSettings/index.js new file mode 100644 index 00000000..cd975bcd --- /dev/null +++ b/src/common/hooks/useVendorCodeGeneratorSettings/index.js @@ -0,0 +1 @@ +export { useVendorCodeGeneratorSettings } from './useVendorCodeGeneratorSettings'; diff --git a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js new file mode 100644 index 00000000..b77087c0 --- /dev/null +++ b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js @@ -0,0 +1,43 @@ +import { useQuery } from 'react-query'; + +import { + useNamespace, + useOkapiKy, + useStripes, +} from '@folio/stripes/core'; + +import { MOD_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; + +export const useVendorCodeGeneratorSettings = () => { + const ky = useOkapiKy(); + const stripes = useStripes(); + const enabled = !!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}`, + limit: 1, + }; + const queryFn = () => ky.get(MOD_SETTINGS_API, { searchParams }).json(); + const { data, isFetching, isLoading } = useQuery([namespace], queryFn, { enabled }); + + const settingValue = data?.items?.[0]?.value; + + return { + isUseGenerator: settingValue === GENERATOR, + isUseTextField: settingValue === TEXTFIELD, + isUseBoth: settingValue === BOTH, + enabled, + isFetching, + isLoading, + }; +}; diff --git a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js new file mode 100644 index 00000000..565f25cd --- /dev/null +++ b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.test.js @@ -0,0 +1,62 @@ +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; + +import { + renderHook, + waitFor, +} from '@folio/jest-config-stripes/testing-library/react'; +import { + useOkapiKy, + useStripes, +} from '@folio/stripes/core'; + +import { useVendorCodeGeneratorSettings } from './useVendorCodeGeneratorSettings'; + +const mockKy = { + get: jest.fn(() => ({ + json: jest.fn(() => Promise.resolve({ + items: [{ value: 'useGenerator' }], + })), + })), +}; + +const mockStripes = { + hasInterface: jest.fn(() => true), +}; + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => {children}; + +describe('useVendorCodeGeneratorSettings', () => { + beforeEach(() => { + jest.clearAllMocks(); + queryClient.clear(); + useOkapiKy.mockReturnValue(mockKy); + useStripes.mockReturnValue(mockStripes); + }); + + it('should return correct values', async () => { + const { result } = renderHook(() => useVendorCodeGeneratorSettings(), { wrapper }); + + await waitFor(() => expect(result.current.isLoading).toBe(false)); + expect(mockKy.get).toHaveBeenCalled(); + expect(result.current.isUseGenerator).toBe(true); + expect(result.current.isUseTextField).toBe(false); + expect(result.current.isUseBoth).toBe(false); + expect(result.current.enabled).toBe(true); + }); + + it('should return enabled=false if interface not present', async () => { + mockStripes.hasInterface.mockImplementationOnce(() => false); + const { result } = renderHook(() => useVendorCodeGeneratorSettings(), { wrapper }); + + await waitFor(() => expect(result.current.isLoading).toBe(false)); + expect(mockKy.get).not.toHaveBeenCalled(); + expect(result.current.isUseGenerator).toBe(false); + expect(result.current.isUseTextField).toBe(false); + expect(result.current.isUseBoth).toBe(false); + expect(result.current.enabled).toBe(false); + }); +}); From 1454363f4d6333c507f941c8d10992a103d2e72c Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Wed, 20 Nov 2024 02:47:56 +0100 Subject: [PATCH 19/23] Update CHANGELOG and version --- CHANGELOG.md | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60577005..acb5149f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change history for ui-organizations -## 5.3.0 (IN PROGRESS) +## 6.0.0 (IN PROGRESS) +* *BREAKING* Add number generator for vendor code including settings page. Refs UIORGS-336, UIORGS-337. ## [5.2.0](https://github.com/folio-org/ui-organizations/tree/v5.2.0) (2024-10-31) [Full Changelog](https://github.com/folio-org/ui-organizations/compare/v5.1.1...v5.2.0) diff --git a/package.json b/package.json index 89d47e85..05d77ebc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@folio/organizations", - "version": "5.2.0", + "version": "6.0.0", "description": "Organizations", "main": "index.js", "repository": "folio-org/ui-organizations", From 0ced3e6bd1465e6406f58947dd47e34dc3eb4991 Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Fri, 22 Nov 2024 15:05:06 +0100 Subject: [PATCH 20/23] Ignore test until fixed --- .../OrganizationBankingInfoForm.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js b/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js index bcc3786d..9708bcbf 100644 --- a/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js +++ b/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js @@ -97,7 +97,8 @@ describe('OrganizationBankingInfoForm', () => { }); describe('Interaction with \'Addresses\' categories fields', () => { - it('should render categories options based on selected address categories', async () => { + // ignore until master is fixed + xit('should render categories options based on selected address categories', async () => { renderOrganizationBankingInfoForm(); await addField(); From b398e75531c8dd01646022ca58d947ee878c3257 Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Wed, 27 Nov 2024 09:02:27 +0100 Subject: [PATCH 21/23] Unignore test --- .../OrganizationBankingInfoForm.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js b/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js index c1183c73..605b24bf 100644 --- a/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js +++ b/src/Organizations/OrganizationForm/OrganizationBankingInfoForm/OrganizationBankingInfoForm.test.js @@ -97,8 +97,7 @@ describe('OrganizationBankingInfoForm', () => { }); describe('Interaction with \'Addresses\' categories fields', () => { - // ignore until master is fixed - xit('should render categories options based on selected address categories', async () => { + it('should render categories options based on selected address categories', async () => { renderOrganizationBankingInfoForm(); await addField(); From d6433040d3f2f5c883b8b4069093a5758c29706b Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Mon, 9 Dec 2024 17:07:24 +0100 Subject: [PATCH 22/23] 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", From 51224b5be51b92d0c0824b3588efbc0925191ccd Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Wed, 11 Dec 2024 09:48:59 +0100 Subject: [PATCH 23/23] Add AbortSignal to queryFn --- .../useVendorCodeGeneratorSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js index 28b72c4c..a2934c8a 100644 --- a/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js +++ b/src/common/hooks/useVendorCodeGeneratorSettings/useVendorCodeGeneratorSettings.js @@ -26,7 +26,7 @@ export const useVendorCodeGeneratorSettings = () => { query: `key==${VENDOR_CODE_GENERATOR_SETTINGS_KEY}`, limit: 1, }; - const queryFn = () => ky.get(SETTINGS_API, { searchParams }).json(); + const queryFn = ({ signal }) => ky.get(SETTINGS_API, { searchParams, signal }).json(); const { data, isFetching, isLoading } = useQuery([namespace], queryFn, { enabled }); const vendorCodeSetting = data?.settings?.[0];