Skip to content

Commit

Permalink
UIQM-697: Field 008: Validate the length of subfields. Add backslashe…
Browse files Browse the repository at this point in the history
…s if the length of a subfield of field 008 is shorter, if longer - cut off the extra characters. (#727)
  • Loading branch information
Dmytro-Melnyshyn authored Sep 18, 2024
1 parent 6a773ae commit 0cbc1ec
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* [UIQM-665](https://issues.folio.org/browse/UIQM-665) Fix to generate array content in 008 after changing document type of MARC bib.
* [UIQM-694](https://issues.folio.org/browse/UIQM-694) Separate error messages triggered by controlled subfields of different linked fields.
* [UIQM-592](https://issues.folio.org/browse/UIQM-592) Fix to input polish special chars into fields.
* [UIQM-697](https://issues.folio.org/browse/UIQM-697) Field 008: Validate the length of subfields. Add backslashes if the length of a subfield of field 008 is shorter, if longer - cut off the extra characters.

## [8.0.1] (https://github.com/folio-org/ui-quick-marc/tree/v8.0.1) (2024-04-18)

Expand Down
8 changes: 7 additions & 1 deletion src/QuickMarcEditor/QuickMarcCreateWrapper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
holdingsLeader,
} from '../../test/jest/fixtures/leaders';
import fixedFieldSpecBib from '../../test/mocks/fixedFieldSpecBib';
import fixedFieldSpecAuth from '../../test/mocks/fixedFieldSpecAuth';

const runWithDelayedPromise = (fn, delay) => () => {
return new Promise(resolve => setTimeout(() => resolve(fn()), delay));
Expand Down Expand Up @@ -232,6 +233,11 @@ const mockFormValues = jest.fn((marcType) => ({
updateInfo: { recordState: 'NEW' },
}));

const mockSpecs = {
[MARC_TYPES.BIB]: fixedFieldSpecBib,
[MARC_TYPES.AUTHORITY]: fixedFieldSpecAuth,
};

jest.mock('@folio/stripes/final-form', () => () => (Component) => ({
onSubmit,
marcType,
Expand Down Expand Up @@ -311,7 +317,7 @@ const renderQuickMarcCreateWrapper = ({
mutator={mutator}
action={QUICK_MARC_ACTIONS.CREATE}
marcType={marcType}
fixedFieldSpec={fixedFieldSpecBib}
fixedFieldSpec={mockSpecs[marcType]}
initialValues={mockFormValues(marcType)}
locations={locations}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/QuickMarcEditor/QuickMarcDeriveWrapper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const mockFormValues = jest.fn(() => ({
Cont: ['b', '\\', '\\', '\\'],
Ctry: 'miu',
Date1: '2009',
Date2: '\\\\',
Date2: '\\\\\\\\',
Desc: 'i',
DtSt: 's',
Entered: '130325',
Expand Down
8 changes: 7 additions & 1 deletion src/QuickMarcEditor/QuickMarcEditWrapper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
bibLeaderString,
} from '../../test/jest/fixtures/leaders';
import fixedFieldSpecBib from '../../test/mocks/fixedFieldSpecBib';
import fixedFieldSpecAuth from '../../test/mocks/fixedFieldSpecAuth';

jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
Expand Down Expand Up @@ -277,6 +278,11 @@ const mockFormValues = jest.fn((marcType) => ({
updateInfo: { recordState: 'NEW' },
}));

const mockSpecs = {
[MARC_TYPES.BIB]: fixedFieldSpecBib,
[MARC_TYPES.AUTHORITY]: fixedFieldSpecAuth,
};

const mockActualizeLinks = jest.fn((formValuesToProcess) => Promise.resolve(formValuesToProcess));
const mockUpdateMarcRecord = jest.fn().mockResolvedValue();
const mockOnCheckCentralTenantPerm = jest.fn().mockReturnValue(false);
Expand Down Expand Up @@ -383,7 +389,7 @@ const renderQuickMarcEditWrapper = ({
locations={locations}
externalRecordPath="/some-record"
refreshPageData={jest.fn().mockResolvedValue()}
fixedFieldSpec={fixedFieldSpecBib}
fixedFieldSpec={mockSpecs[marcType]}
onCheckCentralTenantPerm={mockOnCheckCentralTenantPerm}
{...renderProps}
{...props}
Expand Down
6 changes: 6 additions & 0 deletions src/QuickMarcEditor/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1228,3 +1228,9 @@ export const isDiacritic = (char) => {

return char.normalize('NFD') !== char;
};

export const getVisibleNonSelectable008Subfields = (fixedFieldType) => {
return fixedFieldType.items
.filter(field => !field.readOnly)
.filter(field => !field.isArray);
};
12 changes: 12 additions & 0 deletions src/hooks/useValidation/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
validateContentExistence,
validateFixedFieldPositions,
validateLccnDuplication,
validateFixedFieldLength,
} from './validators';
import {
is010LinkedToBibRecord,
Expand All @@ -52,6 +53,7 @@ const RULES = {
SUBFIELD_VALUE_MATCH: validateSubfieldValueMatch,
SUBFIELD_CHANGED: validateSubfieldChanged,
FIXED_FIELD_POSITIONS: validateFixedFieldPositions,
FIXED_FIELD_LENGTH: validateFixedFieldLength,
DUPLICATE_LCCN: validateLccnDuplication,
};

Expand Down Expand Up @@ -84,6 +86,11 @@ const BASE_BIB_VALIDATORS = [
validator: RULES.FIXED_FIELD_POSITIONS,
message: (name) => ({ id: 'ui-quick-marc.record.error.008.invalidValue', values: { name } }),
},
{
tag: '008',
validator: RULES.FIXED_FIELD_LENGTH,
message: (name, length) => ({ id: 'ui-quick-marc.record.error.008.invalidLength', values: { name, length } }),
},
{
validator: RULES.$9IN_LINKABLE,
message: () => ({ id: 'ui-quick-marc.record.error.$9' }),
Expand Down Expand Up @@ -258,6 +265,11 @@ const BASE_AUTHORITY_VALIDATORS = [
validator: RULES.DUPLICATE_LCCN,
message: () => ({ id: 'ui-quick-marc.record.error.010.lccnDuplicated' }),
},
{
tag: '008',
validator: RULES.FIXED_FIELD_LENGTH,
message: (name, length) => ({ id: 'ui-quick-marc.record.error.008.invalidLength', values: { name, length } }),
},
];

const CREATE_AUTHORITY_VALIDATORS = [
Expand Down
59 changes: 55 additions & 4 deletions src/hooks/useValidation/useValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
useCallback,
useContext,
} from 'react';
import { useIntl } from 'react-intl';
import flow from 'lodash/flow';

import { useOkapiKy } from '@folio/stripes/core';
Expand All @@ -13,6 +14,8 @@ import {
useValidate,
} from '../../queries';
import {
getLeaderPositions,
getVisibleNonSelectable008Subfields,
isLeaderRow,
joinErrors,
} from '../../QuickMarcEditor/utils';
Expand All @@ -21,7 +24,11 @@ import {
MISSING_FIELD_ID,
SEVERITY,
} from './constants';
import { QUICK_MARC_ACTIONS } from '../../QuickMarcEditor/constants';
import {
FIXED_FIELD_TAG,
QUICK_MARC_ACTIONS,
} from '../../QuickMarcEditor/constants';
import { FixedFieldFactory } from '../../QuickMarcEditor/QuickMarcEditorRows/FixedField';

const BE_VALIDATION_MARC_TYPES = [MARC_TYPES.BIB, MARC_TYPES.AUTHORITY];

Expand All @@ -46,6 +53,7 @@ const useValidation = (context = {}) => {
const { validate: validateFetch } = useValidate();
const { duplicateLccnCheckingEnabled } = useLccnDuplicateConfig({ marcType: context.marcType });
const ky = useOkapiKy();
const intl = useIntl();

const runFrontEndValidation = useCallback(async (marcRecords) => {
const validationRules = validators[context.marcType][context.action];
Expand All @@ -56,11 +64,12 @@ const useValidation = (context = {}) => {
marcRecords,
duplicateLccnCheckingEnabled,
ky,
intl,
}, rule)))
.then(errorsList => errorsList.reduce((joinedErrors, ruleErrors) => joinErrors(joinedErrors, ruleErrors), {}));

return formatFEValidation(errors);
}, [context, quickMarcContext, duplicateLccnCheckingEnabled, ky]);
}, [context, quickMarcContext, duplicateLccnCheckingEnabled, ky, intl]);

const formatBEValidationResponse = (response, marcRecords) => {
if (!response.issues) {
Expand Down Expand Up @@ -103,7 +112,49 @@ const useValidation = (context = {}) => {
return issues;
}, [context.action, context.marcType]);

const runBackEndValidation = useCallback(async (marcRecords) => {
// if the length of a subfield of field 008 is shorter, then add backslashes,
// if longer, then cut off the extra characters.
const fillIn008FieldBlanks = useCallback((marcRecords) => {
if (![MARC_TYPES.BIB, MARC_TYPES.AUTHORITY].includes(context.marcType)) {
return marcRecords;
}

const { type, position7 } = getLeaderPositions(context.marcType, marcRecords);
const fixedFieldType = FixedFieldFactory.getFixedFieldType(context.fixedFieldSpec, type, position7);

const fieldsMap = getVisibleNonSelectable008Subfields(fixedFieldType)
.reduce((acc, field) => ({ ...acc, [field.code]: field }), {});

return marcRecords.map(field => {
if (field.tag !== FIXED_FIELD_TAG) {
return field;
}

// if the spec contains a subfield length of 4, then '123456' becomes '1234' and '12' becomes '12\\\\'
return {
...field,
content: Object.keys(field.content).reduce((acc, code) => {
const value = field.content[code];

if (Array.isArray(value) || !fieldsMap[code]) {
acc[code] = value;
} else {
const length = fieldsMap[code].length;

acc[code] = value.length === length
? value
: value.substring(0, length).padEnd(length, '\\');
}

return acc;
}, {}),
};
});
}, [context.fixedFieldSpec, context.marcType]);

const runBackEndValidation = useCallback(async (records) => {
const marcRecords = fillIn008FieldBlanks(records);

const body = {
fields: marcRecords.filter(record => !isLeaderRow(record)),
leader: marcRecords.find(isLeaderRow)?.content,
Expand All @@ -116,7 +167,7 @@ const useValidation = (context = {}) => {
() => formatBEValidationResponse(response, marcRecords),
removeError001MissingField,
)();
}, [context, validateFetch, removeError001MissingField]);
}, [context, validateFetch, removeError001MissingField, fillIn008FieldBlanks]);

const isBackEndValidationMarcType = useCallback(marcType => BE_VALIDATION_MARC_TYPES.includes(marcType), []);

Expand Down
Loading

0 comments on commit 0cbc1ec

Please sign in to comment.