diff --git a/src/components/PiecesPreviewModal/PiecesPreviewModal.js b/src/components/PiecesPreviewModal/PiecesPreviewModal.js index 6147b850..050c518d 100644 --- a/src/components/PiecesPreviewModal/PiecesPreviewModal.js +++ b/src/components/PiecesPreviewModal/PiecesPreviewModal.js @@ -82,20 +82,30 @@ const PiecesPreviewModal = ({ .then((res) => history.push(urls.pieceSetView(res?.id))) ); + // Slightly annoying implementation here, for future work we expect to refactor the UserConfiguredMetadata on the backend + // At which point this should be refactored aswell const formatStartingValues = (values) => { - return ruleset?.templateConfig?.rules?.map((rule, ruleIndex) => { - const tmrt = - rule?.templateMetadataRuleType?.value ?? rule?.templateMetadataRuleType; + const startingValuesArray = []; + + ruleset?.templateConfig?.chronologyRules?.forEach((rule, ruleIndex) => { + startingValuesArray.push({ + userConfiguredTemplateMetadataType: 'chronology', + index: ruleIndex, + metadataType: {}, + }); + }); + + ruleset?.templateConfig?.enumerationRules?.forEach((rule, ruleIndex) => { const tmrf = - rule?.ruleType?.templateMetadataRuleFormat?.value ?? - rule?.ruleType?.templateMetadataRuleFormat; - return { - userConfiguredTemplateMetadataType: tmrt, + rule?.templateMetadataRuleFormat?.value ?? + rule?.templateMetadataRuleFormat; + startingValuesArray.push({ + userConfiguredTemplateMetadataType: 'enumeration', index: ruleIndex, metadataType: { ...(tmrf === 'enumeration_numeric' && { levels: - rule?.ruleType?.ruleFormat?.levels?.map((_level, levelIndex) => { + rule?.ruleFormat?.levels?.map((_level, levelIndex) => { return { ...values?.startingValues[ruleIndex]?.levels[levelIndex], index: levelIndex, @@ -103,8 +113,9 @@ const PiecesPreviewModal = ({ }) || {}, }), }, - }; + }); }); + return startingValuesArray; }; // istanbul ignore next @@ -135,6 +146,7 @@ const PiecesPreviewModal = ({ const dateExists = existingPieceSets?.some( (ps) => ps?.startDate === values?.startDate ); + return ( <> {allowCreation && ( @@ -163,7 +175,7 @@ const PiecesPreviewModal = ({ key="preview-predicated-pieces-button" buttonStyle={allowCreation ? 'default' : 'primary'} disabled={submitting || invalid || pristine} - id="rulset-preview-button" + id="ruleset-preview-button" marginBottom0 onClick={() => handleGeneration(values)} type="submit" @@ -184,7 +196,7 @@ const PiecesPreviewModal = ({ ); }; - // TODO what is the point of this + // Added to prevent SonarCloud smells const renderPublicationDate = (piece) => { return ; }; @@ -220,14 +232,9 @@ const PiecesPreviewModal = ({ columnWidths={{ publicationDate: { min: 100, max: 165 }, }} - // DEPRECATED - This handles the older case in which generatePieces responded with an array of pieces - // Now supports newer response of the predicted piece set object, containing an array of pieces - // TODO Remove this once interface version has been increased - contentData={ - Array.isArray(generatedPieceSet) - ? generatedPieceSet - : generatedPieceSet?.pieces - } + // We have removed the Array handling of this as of MODSER-42 which is an interface version bump + // Should be 2.0 I believe, or 1.3, eitherway as of sunflower release this frontend no longer supports older backend interfaces + contentData={generatedPieceSet?.pieces} formatter={formatter} id="pieces-preview-multi-columns" interactive={false} diff --git a/src/components/PiecesPreviewModal/PiecesPreviewModal.test.js b/src/components/PiecesPreviewModal/PiecesPreviewModal.test.js index a1430215..10cad6cc 100644 --- a/src/components/PiecesPreviewModal/PiecesPreviewModal.test.js +++ b/src/components/PiecesPreviewModal/PiecesPreviewModal.test.js @@ -68,7 +68,6 @@ describe('PiecesPreviewModal', () => { translationsProperties ); }); - test('renders the expected modal header', async () => { const { getByText } = renderComponent; expect(getByText('Generate predicted pieces')).toBeInTheDocument(); @@ -105,7 +104,7 @@ describe('PiecesPreviewModal', () => { test('renders the expected label 1 label', async () => { const { getByText } = renderComponent; - expect(getByText('Label 2')).toBeInTheDocument(); + expect(getByText('Label 1')).toBeInTheDocument(); }); test('renders the expected level 1 label', async () => { @@ -129,7 +128,7 @@ describe('PiecesPreviewModal', () => { }); test('renders the preview button', async () => { - await Button({ id: 'rulset-preview-button' }).has({ disabled: true }); + await Button({ id: 'ruleset-preview-button' }).has({ disabled: true }); }); test('renders the close button', async () => { @@ -143,10 +142,10 @@ describe('PiecesPreviewModal', () => { mockMutateAsync.mockClear(); await waitFor(async () => { await Datepicker({ id: 'ruleset-start-date' }).fillIn('01/01/2026'); - await TextField({ name: 'startingValues[1].levels[0].rawValue' }).fillIn( + await TextField({ name: 'startingValues[0].levels[0].rawValue' }).fillIn( '1' ); - await TextField({ name: 'startingValues[1].levels[1].rawValue' }).fillIn( + await TextField({ name: 'startingValues[0].levels[1].rawValue' }).fillIn( '1' ); }); @@ -185,10 +184,10 @@ describe('PiecesPreviewModal', () => { mockMutateAsync.mockClear(); await waitFor(async () => { await Datepicker({ id: 'ruleset-start-date' }).fillIn('02/01/2024'); - await TextField({ name: 'startingValues[1].levels[0].rawValue' }).fillIn( + await TextField({ name: 'startingValues[0].levels[0].rawValue' }).fillIn( '1' ); - await TextField({ name: 'startingValues[1].levels[1].rawValue' }).fillIn( + await TextField({ name: 'startingValues[0].levels[1].rawValue' }).fillIn( '1' ); }); @@ -269,7 +268,7 @@ describe('PiecesPreviewModal w/o allowCreation', () => { }); test('renders the preview button', async () => { - await Button({ id: 'rulset-preview-button' }).has({ disabled: true }); + await Button({ id: 'ruleset-preview-button' }).has({ disabled: true }); }); // TODO better name for this @@ -277,10 +276,10 @@ describe('PiecesPreviewModal w/o allowCreation', () => { beforeEach(async () => { await waitFor(async () => { await Datepicker({ id: 'ruleset-start-date' }).fillIn('01/01/2025'); - await TextField({ name: 'startingValues[1].levels[0].rawValue' }).fillIn( + await TextField({ name: 'startingValues[0].levels[0].rawValue' }).fillIn( '1' ); - await TextField({ name: 'startingValues[1].levels[1].rawValue' }).fillIn( + await TextField({ name: 'startingValues[0].levels[1].rawValue' }).fillIn( '1' ); }); @@ -293,13 +292,13 @@ describe('PiecesPreviewModal w/o allowCreation', () => { }); test('preview button is not disabled', async () => { - await Button({ id: 'rulset-preview-button' }).has({ disabled: false }); + await Button({ id: 'ruleset-preview-button' }).has({ disabled: false }); }); describe('clicking preview button', () => { beforeEach(async () => { await waitFor(async () => { - await Button({ id: 'rulset-preview-button' }).click(); + await Button({ id: 'ruleset-preview-button' }).click(); }); }); diff --git a/src/components/PiecesPreviewModal/PiecesPreviewModalForm.js b/src/components/PiecesPreviewModal/PiecesPreviewModalForm.js index 482bcc7b..e0ecd2a9 100644 --- a/src/components/PiecesPreviewModal/PiecesPreviewModalForm.js +++ b/src/components/PiecesPreviewModal/PiecesPreviewModalForm.js @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import { Field, useForm, useFormState } from 'react-final-form'; import { FormattedMessage, useIntl } from 'react-intl'; @@ -83,13 +83,16 @@ const PiecesPreviewModalForm = ({ ); }, [ruleset?.recurrence?.period, ruleset?.recurrence?.timeUnit?.value]); - const getAdjustedStartDate = (date, numberOfCycles = 1) => { - const adjustedStartDate = new Date(date); - adjustedStartDate.setFullYear( - adjustedStartDate.getFullYear() + minimumNumberOfYears * numberOfCycles - ); - return adjustedStartDate; - }; + const getAdjustedStartDate = useCallback( + (date, numberOfCycles = 1) => { + const adjustedStartDate = new Date(date); + adjustedStartDate.setFullYear( + adjustedStartDate.getFullYear() + minimumNumberOfYears * numberOfCycles + ); + return adjustedStartDate; + }, + [minimumNumberOfYears] + ); const startingValueDataOptions = existingPieceSets ?.filter((ps) => ps?.ruleset?.id === ruleset?.id) @@ -103,39 +106,47 @@ const PiecesPreviewModalForm = ({ }; }); - const handleStartingValuesChange = (e) => { - const selectedPieceSet = existingPieceSets?.find( - (ps) => ps.id === e?.target?.value || '' - ); - batch(() => { - change( - 'startDate', - selectedPieceSet?.startDate - ? getAdjustedStartDate( - selectedPieceSet?.startDate, - selectedPieceSet?.numberOfCycles ?? 1 - ) - : null - ); - change('numberOfCycles', selectedPieceSet?.numberOfCycles ?? 1); - change( - 'startingValues', - selectedPieceSet?.continuationPieceRecurrenceMetadata?.userConfigured?.map( - (uc) => { - if (uc?.metadataType?.levels?.length) { - return { - levels: uc?.metadataType?.levels?.map((ucl) => { - return { rawValue: ucl?.rawValue }; - }), - }; - } - return null; - } - ) - ); - }); + // Seperated out due to sonarcloud code smells + const formatUserConfiguredMetadata = (userConfiguredMetadata = []) => { + return userConfiguredMetadata + .filter((uc) => uc?.metadataType?.levels?.length) + .map((uc) => { + return { + levels: uc?.metadataType?.levels?.map((ucl) => { + return { rawValue: ucl?.rawValue }; + }), + }; + }); }; + const handleStartingValuesChange = useCallback( + (e) => { + const selectedPieceSet = existingPieceSets?.find( + (ps) => ps.id === e?.target?.value || '' + ); + batch(() => { + change( + 'startDate', + selectedPieceSet?.startDate + ? getAdjustedStartDate( + selectedPieceSet?.startDate, + selectedPieceSet?.numberOfCycles ?? 1 + ) + : null + ); + change('numberOfCycles', selectedPieceSet?.numberOfCycles ?? 1); + change( + 'startingValues', + formatUserConfiguredMetadata( + selectedPieceSet?.continuationPieceRecurrenceMetadata + ?.userConfigured + ) + ); + }); + }, + [batch, change, existingPieceSets, getAdjustedStartDate] + ); + const renderEnumerationNumericField = (formatValues, index) => { return ( @@ -149,7 +160,7 @@ const PiecesPreviewModalForm = ({ - {formatValues?.ruleType?.ruleFormat?.levels?.map((e, i) => { + {formatValues?.ruleFormat?.levels?.map((e, i) => { return (
- {ruleset?.templateConfig?.rules?.map((e, i) => { + {ruleset?.templateConfig?.enumerationRules?.map((e, i) => { if ( - e?.ruleType?.templateMetadataRuleFormat?.value === - 'enumeration_numeric' || - e?.ruleType?.templateMetadataRuleFormat === 'enumeration_numeric' + e?.templateMetadataRuleFormat?.value === 'enumeration_numeric' || + e?.templateMetadataRuleFormat === 'enumeration_numeric' ) { // Required so that sonarcloud doesnt flag use of index within key prop const indexCounter = i; @@ -213,7 +223,7 @@ const PiecesPreviewModalForm = ({ return ( <> - {!!startingValueDataOptions?.length && ( + {startingValueDataOptions?.length > 0 && ( + } + meta={meta} + onChange={(e) => chronologySelectorOnChange(e, index)} + required + /> + ); + }} + validate={requiredValidator} + /> + + + + } + name={`templateConfig.chronologyRules[${index}].ruleLocale`} + onFilter={filterSelectValues} + parse={(v) => v} + required + validate={requiredValidator} + /> + + + + {values?.templateConfig?.chronologyRules[index] + ?.templateMetadataRuleFormat && ( + + )} + + ); + }; + + return ( + <> + + {() => items.map((chronologyRule, index) => { + return renderLabelRule(chronologyRule, index); + }) + } + + {!values?.templateConfig?.chronologyRules?.length > 0 && ( + <> +
+ +
+
+ + )} + + + ); +}; + +export default ChronologyFieldArray; diff --git a/src/components/RulesetFormSections/ChronologyFieldArray/ChronologyFieldArray.test.js b/src/components/RulesetFormSections/ChronologyFieldArray/ChronologyFieldArray.test.js new file mode 100644 index 00000000..fd804fe6 --- /dev/null +++ b/src/components/RulesetFormSections/ChronologyFieldArray/ChronologyFieldArray.test.js @@ -0,0 +1,114 @@ +import { waitFor } from '@folio/jest-config-stripes/testing-library/react'; +import { + renderWithIntl, + TestForm, + Button, + Selection, + Select, +} from '@folio/stripes-erm-testing'; + +import { translationsProperties } from '../../../../test/helpers'; +import mockRefdata from '../../../../test/resources/refdata'; +import { locales } from '../../../../test/resources'; + +import ChronologyFieldArray from './ChronologyFieldArray'; + +jest.mock('../ChronologyField', () => () =>
ChronologyField
); + +const onSubmit = jest.fn(); + +const mockLocales = locales; + +jest.mock('../../../hooks', () => ({ + ...jest.requireActual('../../../hooks'), + useLocales: () => { + return { data: mockLocales }; + }, +})); + +jest.mock('../../utils', () => ({ + ...jest.requireActual('../../utils'), + useSerialsManagementRefdata: () => { + return mockRefdata.filter((mr) => { + return ( + mr.desc === 'ChronologyTemplateMetadataRule.TemplateMetadataRuleFormat' + ); + }); + }, +})); + +let renderComponent; +describe('EnumerationFieldArray', () => { + describe('with no values', () => { + beforeEach(() => { + renderComponent = renderWithIntl( + + + , + translationsProperties + ); + }); + + test('renders the expected empty chronology rules label', async () => { + const { getByText } = renderComponent; + expect( + getByText('No chronology labels for this publication pattern') + ).toBeInTheDocument(); + }); + + test('renders the add chronology rule button', async () => { + await Button('Add chronology label').exists(); + }); + + describe('clicking the add chronology rule button', () => { + beforeEach(async () => { + await waitFor(async () => { + await Button('Add chronology label').click(); + }); + }); + + test('renders an chronology index card', async () => { + const { getByText } = renderComponent; + await waitFor(() => { + expect(getByText('Chronology label 1')).toBeInTheDocument(); + }); + }); + + test('renders the chronology format select field', async () => { + await Select('Chronology format*').exists(); + }); + + test('renders the locale select field', async () => { + await Selection('Locale*').exists(); + }); + + describe('selecting a chronology format value', () => { + beforeEach(async () => { + await waitFor(async () => { + await Select('Chronology format*').choose('Date'); + }); + }); + + test('renders a chronology field', () => { + const { queryByText } = renderComponent; + expect(queryByText('ChronologyField')).toBeInTheDocument(); + }); + }); + + // TODO Broken test + // describe('deleting the chronology label', () => { + // beforeEach(async () => { + // await waitFor(async () => { + // await Button({ id: 'chronology-0-delete-button' }).click(); + // }); + // }); + + // test('no longer renders an chronology index card', () => { + // const { queryByText } = renderComponent; + // screen.debug(); + // expect(queryByText('Chronology label 1')).not.toBeInTheDocument(); + // }); + // }); + }); + }); +}); diff --git a/src/components/RulesetFormSections/ChronologyFieldArray/index.js b/src/components/RulesetFormSections/ChronologyFieldArray/index.js new file mode 100644 index 00000000..d780dfab --- /dev/null +++ b/src/components/RulesetFormSections/ChronologyFieldArray/index.js @@ -0,0 +1 @@ +export { default } from './ChronologyFieldArray'; diff --git a/src/components/RulesetFormSections/EnumerationFieldArray/EnumerationFieldArray.js b/src/components/RulesetFormSections/EnumerationFieldArray/EnumerationFieldArray.js new file mode 100644 index 00000000..139d6397 --- /dev/null +++ b/src/components/RulesetFormSections/EnumerationFieldArray/EnumerationFieldArray.js @@ -0,0 +1,147 @@ +import { useCallback, useMemo } from 'react'; +import { FieldArray } from 'react-final-form-arrays'; +import { useFormState, Field, useForm } from 'react-final-form'; +import { FormattedMessage } from 'react-intl'; + +import { Button, Select, Row, Col } from '@folio/stripes/components'; +import { + EditCard, + requiredValidator, + selectifyRefdata, +} from '@folio/stripes-erm-components'; + +import { useKiwtFieldArray } from '@k-int/stripes-kint-components'; + +import { useSerialsManagementRefdata } from '../../utils'; + +import EnumerationNumericFieldArray from '../EnumerationNumericFieldArray'; +import EnumerationTextualFieldArray from '../EnumerationTextualFieldArray'; + +const [ENUMERATION_LABEL_FORMAT] = [ + 'EnumerationTemplateMetadataRule.TemplateMetadataRuleFormat', +]; + +const EnumerationFieldArray = () => { + const { values } = useFormState(); + const { change } = useForm(); + const { items, onAddField, onDeleteField } = useKiwtFieldArray( + 'templateConfig.enumerationRules' + ); + + const refdataValues = useSerialsManagementRefdata([ENUMERATION_LABEL_FORMAT]); + + const enumerationOptions = useMemo(() => { + return selectifyRefdata(refdataValues, ENUMERATION_LABEL_FORMAT, 'value'); + }, [refdataValues]); + + const enumerationSelectorOnChange = useCallback( + (e, index) => { + change(`templateConfig.enumerationRules[${index}]`, { + templateMetadataRuleFormat: e?.target?.value, + ruleFormat: { + levels: [{}], + }, + }); + }, + [change] + ); + + const renderLabelRule = (templateConfig, index) => { + // Using indexCount to prevent sonarlint from flagging this as an issue + const indexKey = index; + + return ( + + } + header={ + <> + + + } + onDelete={() => onDeleteField(index, templateConfig)} + > + + + { + return ( + - } - meta={meta} - onChange={(e) => { - change(`templateConfig.rules[${index}]`, { - templateMetadataRuleType: e?.target?.value, - }); - if (e?.target?.value === 'chronology') { - change(`templateConfig.rules[${index}].ruleType`, { - ruleLocale: 'en', - }); - } else { - change( - `templateConfig.rules[${index}].ruleType`, - undefined - ); - } - }} - required - /> - )} - validate={requiredValidator} - /> - - {values?.templateConfig?.rules[index]?.templateMetadataRuleType && ( - <> - - { - let selectedDataOptions = []; - if ( - values?.templateConfig?.rules[index] - ?.templateMetadataRuleType === 'chronology' - ) { - selectedDataOptions = selectifyRefdata( - refdataValues, - CHRONOLOGY_LABEL_FORMAT, - 'value' - ); - } else if ( - values?.templateConfig?.rules[index] - ?.templateMetadataRuleType === 'enumeration' - ) { - selectedDataOptions = selectifyRefdata( - refdataValues, - ENUMERATION_LABEL_FORMAT, - 'value' - ); - } - return ( -