Skip to content

Commit

Permalink
UIQM-724 do not group together subfields during linking
Browse files Browse the repository at this point in the history
  • Loading branch information
BogdanDenis committed Oct 29, 2024
1 parent 6c432d3 commit 599ca7d
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
* [UIQM-711](https://issues.folio.org/browse/UIQM-711) Update `validateFixedFieldPositions` to display all 008 field errors instead of one in Bibliographic records.
* [UIQM-712](https://issues.folio.org/browse/UIQM-712) In field 007 for Projected Graphic type: change the `MfS` field type to `Byte` to allow only 1 character to be entered.
* [UIQM-715](https://issues.folio.org/browse/UIQM-715) Reuse existing ids for fields after saving a record to avoid re-rendering and be able to focus on a field by ref.
* [UIQM-724](https://issues.folio.org/browse/UIQM-724) Do not group together subfields during linking.

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

Expand Down
32 changes: 16 additions & 16 deletions src/QuickMarcEditor/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
SOURCES,
} from '../common/constants';
import { leaderConfig } from './QuickMarcEditorRows/LeaderField/leaderConfig';
import { MarcFieldContent } from '../common';

export const isLastRecord = recordRow => {
return (
Expand All @@ -70,6 +71,9 @@ export const isContentRow = (recordRow, marcType) => {
|| isControlNumberRow(recordRow));
};

// returns an object with subfields values. order of subfields is not kept
// '$a valueA1 $a value A2 $b valueB' -> { '$a': ['valueA1', 'valueA2'], '$b': ['valueB'] }

export const getContentSubfieldValue = (content = '') => {
return content.split(/\$/)
.filter(str => str.length > 0)
Expand Down Expand Up @@ -1008,47 +1012,43 @@ export const getCorrespondingMarcTag = (records) => {
};

export const groupSubfields = (field, authorityControlledSubfields = []) => {
const subfields = toPairs(getContentSubfieldValue(field.content));
const subfields = new MarcFieldContent(field.content);
// const subfields = toPairs(getContentSubfieldValue(field.content));

return subfields.reduce((groups, subfield) => {
const isControlled = authorityControlledSubfields.includes(subfield[0].replace('$', ''));
const isNum = /\$\d/.test(subfield[0]);
const isZero = /\$0/.test(subfield[0]);
const isNine = /\$9/.test(subfield[0]);

const fieldContent = subfield[1].reduce((content, value) => [content, `${subfield[0]} ${value}`].join(' ').trimStart(), '');
const isControlled = authorityControlledSubfields.includes(subfield.code.replace('$', ''));
const isNum = /\$\d/.test(subfield.code);
const isZero = /\$0/.test(subfield.code);
const isNine = /\$9/.test(subfield.code);

const formattedSubfield = {
content: fieldContent,
code: subfield[0],
};
const subfieldCodeAndValue = `${subfield.code} ${subfield.value}`;

if (isControlled) {
groups.controlled = [groups.controlled, formattedSubfield.content].join(' ').trim();
groups.controlled = [groups.controlled, subfieldCodeAndValue].join(' ').trim();

return groups;
}

if (!isControlled && !isNum) {
groups[UNCONTROLLED_ALPHA] = [groups[UNCONTROLLED_ALPHA], formattedSubfield.content].join(' ').trim();
groups[UNCONTROLLED_ALPHA] = [groups[UNCONTROLLED_ALPHA], subfieldCodeAndValue].join(' ').trim();

return groups;
}

if (isZero) {
groups.zeroSubfield = [groups.zeroSubfield, formattedSubfield.content].join(' ').trim();
groups.zeroSubfield = [groups.zeroSubfield, subfieldCodeAndValue].join(' ').trim();

return groups;
}

if (isNine) {
groups.nineSubfield = [groups.nineSubfield, formattedSubfield.content].join(' ').trim();
groups.nineSubfield = [groups.nineSubfield, subfieldCodeAndValue].join(' ').trim();

return groups;
}

if (isNum) {
groups[UNCONTROLLED_NUMBER] = [groups[UNCONTROLLED_NUMBER], formattedSubfield.content].join(' ').trim();
groups[UNCONTROLLED_NUMBER] = [groups[UNCONTROLLED_NUMBER], subfieldCodeAndValue].join(' ').trim();

return groups;
}
Expand Down
96 changes: 96 additions & 0 deletions src/common/entities/MarcFieldContent/MarcFieldContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// returns an array of subfields in the same order as in content string
// '$a valueA1 $a value A2 $b valueB' -> [{ '$a': 'valueA1' }, { '$a': 'valueA2' }, { '$b': 'valueB' }]

export const getContentSubfieldValueArr = (content = '') => {
return content.split(/\$/)
.filter(str => str.length > 0)
.reduce((acc, str) => {
if (!str) {
return acc;
}

const key = `$${str[0]}`;
const value = str.substring(2).trim();

return [...acc, { code: key, value }];
}, []);
};

const reduceArr = (arr) => {
return arr.reduce((acc, cur) => {
const { code, value } = cur;
const reducedValues = acc[code];

if (reducedValues) {
return {
...acc,
[code]: [...reducedValues, value],
};
}

return {
...acc,
[code]: [value],
};
}, {});
};

export class MarcFieldContent {
constructor(content) {
this.content = content || '';

this.subfieldsArr = getContentSubfieldValueArr(this.content);

// Proxy allows to define generic property getters
return new Proxy(this, this);
}

map(callback) {
return this.subfieldsArr.map(callback);
}

reduce(...args) {
return this.subfieldsArr.reduce(...args);
}

forEach(callback) {
return this.subfieldsArr.forEach(callback);
}

join() {
return this.subfieldsArr.reduce((acc, cur) => {
return `${acc} ${cur.code} ${cur.value}`;
}, '').trim();
}

append(code, value) {
this.subfieldsArr.push({ code, value });

return this; // return this to be able to chain together method calls
}

prepend(code, value) {
this.subfieldsArr.unshift({ code, value });

return this;
}

removeByCode(code) {
this.subfieldsArr = this.subfieldsArr.filter(subfield => subfield.code !== code);

return this;
}

findAllByCode(code) {
return this.subfieldsArr.filter(subfield => subfield.code === code);
}

get(target, property) {
// should be able to get array of subfields by calling marcFieldContent['$a']
if (property.match(/\$\w$/)) {
return reduceArr(target.findAllByCode(property))[property];
}

return target[property];
}
}
Empty file.
1 change: 1 addition & 0 deletions src/common/entities/MarcFieldContent/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MarcFieldContent';
1 change: 1 addition & 0 deletions src/common/entities/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MarcFieldContent';
2 changes: 2 additions & 0 deletions src/common/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './constants';
export * from './entities';
110 changes: 74 additions & 36 deletions src/hooks/useAuthorityLinking/useAuthorityLinking.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
useAuthoritySourceFiles,
useLinkSuggestions,
} from '../../queries';

import {
getContentSubfieldValue,
groupSubfields,
Expand All @@ -25,6 +24,7 @@ import {
isFieldLinked,
applyCentralTenantInHeaders,
} from '../../QuickMarcEditor/utils';
import { MarcFieldContent } from '../../common';
import {
AUTOLINKING_STATUSES,
UNCONTROLLED_ALPHA,
Expand Down Expand Up @@ -71,34 +71,70 @@ const useAuthorityLinking = ({ tenantId, marcType, action } = {}) => {

const copySubfieldsFromAuthority = useCallback((bibSubfields, authField, bibTag) => {
const linkingRule = findLinkingRule(bibTag, authField.tag);
const authSubfields = getContentSubfieldValue(authField.content);
let subfieldsFromAuthority = {};
const authSubfields = new MarcFieldContent(authField.content);
const subfieldsAfterLinking = new MarcFieldContent();

const isSubfieldControlled = (subfieldLetter) => {
return linkingRule.authoritySubfields.includes(subfieldLetter)
|| linkingRule.subfieldModifications.find(mod => mod.target === subfieldLetter);
};

/*
Rules for linking fields are:
1. Iterate over authority subfields.
2. If a subfield is not controlled - don't add it to linked field
3. If a subfield is modified - apply the modification rule and add it to linked field
4. If a subfield is a target of modification - don't add it to linked field
5. If a subfield is not modified - add it to linked field
6. Iterate over bib subfields
7. If a subfield is controlled - don't add it to linked field
8. If a subfield is uncontrolled - add it to linked field
*/

authSubfields.forEach(subfield => {
const subfieldLetter = subfield.code.replace('$', '');
const isControlled = isSubfieldControlled(subfieldLetter);
const subfieldModification = linkingRule.subfieldModifications
?.find(mod => mod.source === subfieldLetter);

// if authority subfield is a target of modification then don't add it to linked field
const isSubfieldTarget = linkingRule.subfieldModifications
?.find(mod => mod.target === subfieldLetter);

if (isSubfieldTarget) {
return;
}

linkingRule.authoritySubfields.forEach(subfieldCode => {
const subfieldModification = linkingRule.subfieldModifications?.find(mod => mod.source === subfieldCode);
if (!isControlled) {
return;
}

if (subfieldModification) {
subfieldsFromAuthority[formatSubfieldCode(subfieldModification.target)] =
authSubfields[formatSubfieldCode(subfieldCode)];
} else if (authSubfields[formatSubfieldCode(subfieldCode)]?.[0]) {
subfieldsFromAuthority[formatSubfieldCode(subfieldCode)] = authSubfields[formatSubfieldCode(subfieldCode)];
// special case of modification, where $t becomes $a and should therefore be the first subfield
// in this case we'll pre-pend the subfield
if (bibTag === '240' && subfieldLetter === 't' && subfieldModification.source === 't' && subfieldModification.target === 'a') {
subfieldsAfterLinking.removeByCode('$a');
subfieldsAfterLinking.prepend('$a', subfield.value);
} else {
subfieldsAfterLinking.append(formatSubfieldCode(subfieldModification.target), subfield.value);
}
} else {
delete bibSubfields[formatSubfieldCode(subfieldCode)];
subfieldsAfterLinking.append(subfield.code, subfield.value);
}
});

if (bibTag === '240' && linkingRule.subfieldModifications?.find(mod => mod.source === 't')?.target === 'a') {
subfieldsFromAuthority = {
$a: subfieldsFromAuthority.$a,
...omit(subfieldsFromAuthority, '$a'),
};
}
bibSubfields.forEach(subfield => {
const subfieldLetter = subfield.code.replace('$', '');
const isControlled = isSubfieldControlled(subfieldLetter);

// take authority subfields first and then bib subfields
return {
...subfieldsFromAuthority,
...omit(bibSubfields, Object.keys(subfieldsFromAuthority)),
};
if (isControlled) {
return;
}

subfieldsAfterLinking.append(subfield.code, subfield.value);
});

return subfieldsAfterLinking;
}, [findLinkingRule]);

const getLinkableAuthorityField = useCallback((authoritySource, bibField) => {
Expand Down Expand Up @@ -138,7 +174,7 @@ const useAuthorityLinking = ({ tenantId, marcType, action } = {}) => {
}, [findLinkingRule]);

const updateBibFieldWithLinkingData = useCallback((bibField, linkedAuthorityField, authorityRecord) => {
const bibSubfields = getContentSubfieldValue(bibField.content);
const bibSubfields = new MarcFieldContent(bibField.content);
const sourceFile = sourceFiles.find(file => file.id === authorityRecord.sourceFileId);

let newZeroSubfield = '';
Expand All @@ -149,13 +185,15 @@ const useAuthorityLinking = ({ tenantId, marcType, action } = {}) => {
newZeroSubfield = authorityRecord.naturalId;
}

bibSubfields.$0 = [newZeroSubfield];
bibSubfields.removeByCode('$0');
bibSubfields.append('$0', newZeroSubfield);

const updatedBibSubfields = copySubfieldsFromAuthority(bibSubfields, linkedAuthorityField, bibField.tag);

updatedBibSubfields.$9 = [authorityRecord.id];
updatedBibSubfields.removeByCode('$9');
updatedBibSubfields.append('$9', authorityRecord.id);
bibField.prevContent = bibField.content;
bibField.content = joinSubfields(updatedBibSubfields);
bibField.content = updatedBibSubfields.join();
}, [copySubfieldsFromAuthority, sourceFiles]);

const getSubfieldGroups = useCallback((field, suggestedField) => {
Expand All @@ -170,15 +208,15 @@ const useAuthorityLinking = ({ tenantId, marcType, action } = {}) => {
}, [linkingRules]);

const updateLinkedField = useCallback((field) => {
const uncontrolledNumberSubfields = getContentSubfieldValue(field.subfieldGroups?.[UNCONTROLLED_NUMBER]);
const uncontrolledAlphaSubfields = getContentSubfieldValue(field.subfieldGroups?.[UNCONTROLLED_ALPHA]);
const uncontrolledNumberSubfields = new MarcFieldContent(field.subfieldGroups?.[UNCONTROLLED_NUMBER]);
const uncontrolledAlphaSubfields = new MarcFieldContent(field.subfieldGroups?.[UNCONTROLLED_ALPHA]);

const uncontrolledNumber = uncontrolledNumberSubfields.$9?.[0]
? joinSubfields(omit(uncontrolledNumberSubfields, '$9'))
? uncontrolledNumberSubfields.removeByCode('$9').join()
: field.subfieldGroups[UNCONTROLLED_NUMBER];

const uncontrolledAlpha = uncontrolledAlphaSubfields.$9?.[0]
? joinSubfields(omit(uncontrolledAlphaSubfields, '$9'))
? uncontrolledAlphaSubfields.removeByCode('$9').join()
: field.subfieldGroups[UNCONTROLLED_ALPHA];

return {
Expand All @@ -192,15 +230,15 @@ const useAuthorityLinking = ({ tenantId, marcType, action } = {}) => {
}, []);

const updateAutoLinkableField = useCallback((field, suggestedField) => {
const subfields = new MarcFieldContent(field.content);

if (
suggestedField.linkDetails?.status === AUTOLINKING_STATUSES.ERROR
&& getContentSubfieldValue(field.content).$9?.[0]
&& subfields.$9?.[0]
) {
const subfields = getContentSubfieldValue(field.content);

return {
...field,
content: joinSubfields(omit(subfields, '$9')),
content: subfields.removeByCode('$9').join(),
};
}

Expand Down Expand Up @@ -295,13 +333,13 @@ const useAuthorityLinking = ({ tenantId, marcType, action } = {}) => {
]);

const unlinkAuthority = useCallback((field) => {
const bibSubfields = getContentSubfieldValue(field.content);
const bibSubfields = new MarcFieldContent(field.content);

delete bibSubfields.$9;
bibSubfields.removeByCode('$9');
delete field.linkDetails;
delete field.subfieldGroups;

field.content = field.prevContent ?? joinSubfields(bibSubfields);
field.content = field.prevContent ?? bibSubfields.join();
delete field.prevContent;

return {
Expand Down
Loading

0 comments on commit 599ca7d

Please sign in to comment.