Skip to content

Commit 5bb65d3

Browse files
authored
UIQM-790 Allow to initialize quickMARC with pre-edited values. (#824)
* UIQM-790 Allow to initialize quickMARC with pre-edited values. * UIQM-790 tests for initializing with pre-edited values * UIQM-790 changes after review * UIQM-790 fix eslint
1 parent 07370fd commit 5bb65d3

File tree

11 files changed

+122
-10
lines changed

11 files changed

+122
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* [UIQM-783](https://issues.folio.org/browse/UIQM-783) Update label for 38 position of MARC authority "008" field to "Mod Rec".
1212
* [UIQM-788](https://issues.folio.org/browse/UIQM-788) Add new props to quickMARC to make it usable without having to define routes.
1313
* [UIQM-789](https://issues.folio.org/browse/UIQM-789) Add new `initialValues` prop to quickMARC to initialize it with some pre-existing values.
14+
* [UIQM-790](https://issues.folio.org/browse/UIQM-790) Allow to initialize quickMARC with pre-edited values.
1415

1516
## [10.0.4](https://github.com/folio-org/ui-quick-marc/tree/v10.0.4) (2025-07-14)
1617

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ of the Module Developer's Guide.
3333
| `onCreateAndKeepEditing` | function | Called after creating/deriving a record via "Save and keep editing" button. Called with `externalId` when `marcType` is "bibliographic" or "authority". For `marcType` "holdings" it's called with `instanceId/externalId` | Yes for route-less, No for pre-defined routes |
3434
| `useRoutes` | bool | When `true` - quickMARC will define it's own routes that the consuming application will have to redirect to. When `false` - quickMARC will act like a regular plug-in and simply render a view, and the consuming application will have to define it's own routes and provide some props to quickMARC. | No |
3535
| `initialValues` | object | Values to initialize quickMARC with. Shape should match the response from `records-editor/records` endpoint. Will only be applied when `useRoutes` is `false`. | No |
36+
| `isPreEdited` | bool | Tells quickMARC that `initialValues` is a pre-edited MARC record. In this case quickMARC will fetch a MARC record from BE and first initialize with it, and then replace fields with fields from `initialValues`. This prop will only be applied when `useRoutes` is `false`. | No |
37+
3638

3739

3840
This is a [Stripes](https://github.com/folio-org/stripes-core/) UI module to edit MARC records.

src/QuickMarc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const QuickMarc = ({
3131
initialValues,
3232
onCreateAndKeepEditing = noop,
3333
useRoutes = true,
34+
isPreEdited = false,
3435
}) => {
3536
const location = useLocation();
3637

@@ -132,6 +133,7 @@ const QuickMarc = ({
132133
externalId={externalId}
133134
instanceId={instanceId}
134135
initialValues={initialValues}
136+
isPreEdited={isPreEdited}
135137
/>
136138
</QuickMarcProvider>
137139
)}
@@ -173,6 +175,7 @@ QuickMarc.propTypes = {
173175
}).isRequired,
174176
}).isRequired,
175177
}),
178+
isPreEdited: PropTypes.bool,
176179
};
177180

178181
export default QuickMarc;

src/QuickMarcEditor/AutoLinkingButton/AutoLinkingButton.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const AutoLinkingButton = ({
4343
autoLinkAuthority,
4444
} = useAuthorityLinking({ marcType, action });
4545

46-
const hasAutoLinkableRecord = formValues.records.some(field => isRecordForAutoLinking(field, autoLinkableBibFields));
46+
const hasAutoLinkableRecord = formValues.records?.some(field => isRecordForAutoLinking(field, autoLinkableBibFields));
4747

4848
const getAutoLinkingToasts = (fields) => {
4949
const toasts = [];

src/QuickMarcEditor/QuickMarcEditor.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import React, {
88
} from 'react';
99
import { FormSpy } from 'react-final-form';
1010
import PropTypes from 'prop-types';
11-
import { FormattedMessage, useIntl } from 'react-intl';
11+
import {
12+
FormattedMessage,
13+
useIntl,
14+
} from 'react-intl';
1215
import find from 'lodash/find';
1316
import noop from 'lodash/noop';
1417
import isEmpty from 'lodash/isEmpty';
@@ -130,6 +133,7 @@ const QuickMarcEditor = ({
130133
continueAfterSave,
131134
validationErrorsRef,
132135
isShared,
136+
preEditedValues,
133137
} = useContext(QuickMarcContext);
134138
const { hasErrorIssues, isBackEndValidationMarcType } = useValidation();
135139

@@ -145,6 +149,15 @@ const QuickMarcEditor = ({
145149

146150
const { unlinkAuthority } = useAuthorityLinking({ marcType, action });
147151

152+
useEffect(() => {
153+
// final-form doesn't allow us to initialize a form with some values and make it not pristine
154+
// so this is a work-around for that: initialize the form with data from BE
155+
// and then replace fields in the form with fields from `initialValues` prop
156+
if (preEditedValues) {
157+
mutators.initializeWithPreEditedValues(preEditedValues.records);
158+
}
159+
}, [preEditedValues, mutators]);
160+
148161
useEffect(() => {
149162
setIsValidatedCurrentValues(false);
150163
}, [formValues, setIsValidatedCurrentValues]);
@@ -725,6 +738,9 @@ QuickMarcEditor.propTypes = {
725738
export default stripesFinalForm({
726739
navigationCheck: true,
727740
mutators: {
741+
initializeWithPreEditedValues: ([records], state, tools) => {
742+
tools.changeValue(state, 'records', () => records);
743+
},
728744
addRecord: ([{ index }], state, tools) => {
729745
const records = addNewRecord(index, state);
730746

src/QuickMarcEditor/QuickMarcEditor.test.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const onCloseMock = jest.fn();
8585
const onSubmitMock = jest.fn(() => Promise.resolve({ version: 1 }));
8686
const mockShowCallout = jest.fn();
8787
const mockValidate = jest.fn().mockReturnValue({});
88+
const mockInitializeWithPreEditedValues = jest.fn();
8889

8990
useShowCallout.mockClear().mockReturnValue(mockShowCallout);
9091

@@ -157,6 +158,7 @@ const renderQuickMarcEditor = (props = {}, { quickMarcContext } = {}) => (render
157158
addRecord: jest.fn(),
158159
deleteRecord: jest.fn(),
159160
moveRecord: jest.fn(),
161+
initializeWithPreEditedValues: mockInitializeWithPreEditedValues,
160162
}}
161163
initialValues={initialValues}
162164
marcType={MARC_TYPES.BIB}
@@ -683,7 +685,7 @@ describe('Given QuickMarcEditor', () => {
683685

684686
expect(screen.getByText('ui-quick-marc.validation.modal.heading')).toBeInTheDocument();
685687

686-
jest.clearAllTimers();
688+
jest.useRealTimers();
687689
});
688690
});
689691

@@ -702,6 +704,8 @@ describe('Given QuickMarcEditor', () => {
702704
await act(async () => jest.advanceTimersByTime(2100));
703705

704706
expect(screen.queryByText('ui-quick-marc.validation.modal.heading')).not.toBeInTheDocument();
707+
708+
jest.useRealTimers();
705709
});
706710
});
707711

@@ -722,6 +726,8 @@ describe('Given QuickMarcEditor', () => {
722726

723727
expect(screen.queryByText('ui-quick-marc.validation.modal.heading')).not.toBeInTheDocument();
724728
expect(onSubmitMock).not.toHaveBeenCalled();
729+
730+
jest.useRealTimers();
725731
});
726732
});
727733
});
@@ -1627,4 +1633,30 @@ describe('Given QuickMarcEditor', () => {
16271633
expect(queryByText('Confirmation modal')).toBeNull();
16281634
});
16291635
});
1636+
1637+
describe('when QuickMarcContext has preEditedValues', () => {
1638+
it('should call initializeWithPreEditedValues', async () => {
1639+
renderQuickMarcEditor({
1640+
quickMarcContext: {
1641+
preEditedValues: {
1642+
records: [{ tag: '245' }],
1643+
},
1644+
},
1645+
});
1646+
1647+
waitFor(() => expect(mockInitializeWithPreEditedValues).toHaveBeenCalledWith([{ tag: '245' }]));
1648+
});
1649+
});
1650+
1651+
describe('when QuickMarcContext does not have preEditedValues', () => {
1652+
it('should not call initializeWithPreEditedValues', async () => {
1653+
renderQuickMarcEditor({
1654+
quickMarcContext: {
1655+
preEditedValues: null,
1656+
},
1657+
});
1658+
1659+
waitFor(() => expect(mockInitializeWithPreEditedValues).not.toHaveBeenCalled());
1660+
});
1661+
});
16301662
});

src/QuickMarcEditor/QuickMarcEditorContainer.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const propTypes = {
6565
externalId: PropTypes.string.isRequired,
6666
updateInfo: PropTypes.object.isRequired,
6767
}),
68+
isPreEdited: PropTypes.bool.isRequired,
6869
};
6970

7071
const createRecordDefaults = {
@@ -86,6 +87,7 @@ const QuickMarcEditorContainer = ({
8687
onCheckCentralTenantPerm = noop,
8788
match,
8889
initialValues: initialValuesProp,
90+
isPreEdited,
8991
}) => {
9092
const {
9193
action,
@@ -94,6 +96,7 @@ const QuickMarcEditorContainer = ({
9496
instance,
9597
setInstance,
9698
setMarcRecord,
99+
setPreEditedValues,
97100
setRelatedRecordVersion,
98101
getIsShared,
99102
isUsingRouter,
@@ -217,7 +220,8 @@ const QuickMarcEditorContainer = ({
217220
]) => {
218221
let dehydratedMarcRecord;
219222

220-
const isShouldUseInitialValuesProp = initialValuesProp && !isUsingRouter;
223+
const isShouldUseInitialValuesProp = initialValuesProp && !isUsingRouter && !isPreEdited;
224+
const isLoadingDataAfterKeepEditing = Boolean(fieldIds);
221225

222226
if (_action === QUICK_MARC_ACTIONS.CREATE) {
223227
if (isShouldUseInitialValuesProp) {
@@ -230,9 +234,7 @@ const QuickMarcEditorContainer = ({
230234
dehydratedMarcRecord = createRecordDefaults[marcType](instanceResponse, fixedFieldSpecResponse);
231235
}
232236
} else {
233-
const isLoadingDataAfterKeepEditing = Boolean(fieldIds);
234-
235-
// if we just saved a record - then we need to ignore `initialValuesProp` to not initialize with old data
237+
// if we're initializing after Save&keep editing - then we need to ignore `initialValuesProp` to not initialize with old data
236238
// otherwise if we're just entering Edit mode - initialize with initial values or values from response
237239
let dataToInitializeWith = null;
238240

@@ -253,6 +255,20 @@ const QuickMarcEditorContainer = ({
253255
setRelatedRecordVersion(instanceResponse?._version);
254256
setInstance(instanceResponse);
255257
setMarcRecord(formatInitialValues(dehydratedMarcRecord, _action, linkingRulesResponse));
258+
259+
// if using pre-edited initial values - then format them the same way and save in quickMARC context
260+
// so we can then update form values with them
261+
if (!isLoadingDataAfterKeepEditing && isPreEdited) {
262+
const dehydratedPreEditedMarcRecord = dehydrateMarcRecordResponse(
263+
initialValuesProp,
264+
marcType,
265+
fixedFieldSpecResponse,
266+
[],
267+
);
268+
269+
setPreEditedValues(formatInitialValues(dehydratedPreEditedMarcRecord, _action, linkingRulesResponse));
270+
}
271+
256272
setLocations(locationsResponse);
257273
setFixedFieldSpec(fixedFieldSpecResponse);
258274
setIsLoading(false);

src/QuickMarcEditor/QuickMarcEditorContainer.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,14 @@ const renderQuickMarcEditorContainer = ({
9191
action = QUICK_MARC_ACTIONS.EDIT,
9292
marcType = MARC_TYPES.BIB,
9393
isUsingRouter,
94+
quickMarcContext,
9495
...props
9596
} = {}) => (render(
9697
<Harness
9798
action={action}
9899
marcType={marcType}
99100
isUsingRouter={isUsingRouter}
101+
quickMarcContext={quickMarcContext}
100102
>
101103
<QuickMarcEditorContainer
102104
externalRecordPath={externalRecordPath}
@@ -651,4 +653,36 @@ describe('Given Quick Marc Editor Container', () => {
651653
});
652654
});
653655
});
656+
657+
describe('when using isPreEdited prop', () => {
658+
const mockSetPreEditedValues = jest.fn();
659+
660+
beforeEach(() => {
661+
mockSetPreEditedValues.mockClear();
662+
663+
renderQuickMarcEditorContainer({
664+
mutator,
665+
quickMarcContext: {
666+
instance,
667+
action: QUICK_MARC_ACTIONS.EDIT,
668+
marcType: MARC_TYPES.BIB,
669+
isUsingRouter: false,
670+
setPreEditedValues: mockSetPreEditedValues,
671+
},
672+
initialValues: {
673+
marcFormat: 'BIBLIOGRAPHIC',
674+
leader: '03109cas\\a2200841\\a\\4500',
675+
fields: [{
676+
tag: '245',
677+
content: '$a value from initial values',
678+
}],
679+
},
680+
isPreEdited: true,
681+
});
682+
});
683+
684+
it('should set preEditedValues in QuickMarcContext', async () => {
685+
await waitFor(() => expect(mockSetPreEditedValues).toHaveBeenCalled());
686+
});
687+
});
654688
});

src/QuickMarcEditor/QuickMarcEditorRows/QuickMarcEditorRows.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,12 @@ const QuickMarcEditorRows = ({
117117
&& action === QUICK_MARC_ACTIONS.EDIT;
118118

119119
const fixedFieldInitialValues = () => {
120-
return initialValues.records.find(record => record.tag === FIXED_FIELD_TAG)?.content || {};
120+
return initialValues?.records.find(record => record.tag === FIXED_FIELD_TAG)?.content || {};
121121
};
122122

123123
const isNewRow = useCallback((row) => {
124-
return !initialValues.records.find(record => record.id === row.id);
125-
}, [initialValues.records]);
124+
return !initialValues?.records.find(record => record.id === row.id);
125+
}, [initialValues?.records]);
126126

127127
const addNewRow = useCallback(({ target }) => {
128128
const index = parseInt(target.dataset.index, 10);

src/contexts/QuickMarcContext/QuickMarcContext.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const QuickMarcProvider = ({
3737
const location = useLocation();
3838
const [instance, setInstance] = useState(null);
3939
const [marcRecord, setMarcRecord] = useState(null);
40+
const [preEditedValues, setPreEditedValues] = useState(null);
4041
const [selectedSourceFile, setSelectedSourceFile] = useState(null);
4142
const [_relatedRecordVersion, setRelatedRecordVersion] = useState();
4243
const validationErrors = useRef({});
@@ -90,6 +91,8 @@ const QuickMarcProvider = ({
9091
isShared: isSharedRef.current,
9192
setIsShared,
9293
getIsShared,
94+
preEditedValues,
95+
setPreEditedValues,
9396
}), [
9497
selectedSourceFile,
9598
setSelectedSourceFile,
@@ -108,6 +111,8 @@ const QuickMarcProvider = ({
108111
isUsingRouter,
109112
setIsShared,
110113
getIsShared,
114+
preEditedValues,
115+
setPreEditedValues,
111116
]);
112117

113118
return (

0 commit comments

Comments
 (0)