Skip to content

Commit

Permalink
UIQM-724 do not group together subfields during linking (#750)
Browse files Browse the repository at this point in the history
* UIQM-724 do not group together subfields during linking

* UIQM-724 added tests for MarcFieldContent

* UIQM-724 renamed MarcFieldContent join to toContentString
  • Loading branch information
BogdanDenis authored Oct 31, 2024
1 parent fc3424b commit ba58778
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* [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-723](https://issues.folio.org/browse/UIQM-723) Rename permissions.
* * [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: 15 additions & 17 deletions src/QuickMarcEditor/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import omit from 'lodash/omit';
import compact from 'lodash/compact';
import isString from 'lodash/isString';
import isNumber from 'lodash/isNumber';
import toPairs from 'lodash/toPairs';
import flatten from 'lodash/flatten';
import flow from 'lodash/flow';
import assignWith from 'lodash/assignWith';
Expand Down Expand Up @@ -38,6 +37,7 @@ import { SUBFIELD_TYPES } from './QuickMarcEditorRows/BytesField';
import getMaterialCharsFieldConfig from './QuickMarcEditorRows/MaterialCharsField/getMaterialCharsFieldConfig';
import getPhysDescriptionFieldConfig from './QuickMarcEditorRows/PhysDescriptionField/getPhysDescriptionFieldConfig';
import { FixedFieldFactory } from './QuickMarcEditorRows/FixedField';
import { MarcFieldContent } from '../common';
import {
MARC_TYPES,
ERROR_TYPES,
Expand Down Expand Up @@ -70,6 +70,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 +1011,42 @@ export const getCorrespondingMarcTag = (records) => {
};

export const groupSubfields = (field, authorityControlledSubfields = []) => {
const subfields = toPairs(getContentSubfieldValue(field.content));
const subfields = new MarcFieldContent(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);
}

toContentString() {
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];
}
}
95 changes: 95 additions & 0 deletions src/common/entities/MarcFieldContent/MarcFieldContent.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { MarcFieldContent } from './MarcFieldContent';

describe('MarcFieldContent', () => {
const content = '$a a1 $b b1 $b b2 $a a2';

let marcFieldContent = null;

beforeEach(() => {
marcFieldContent = new MarcFieldContent(content);
});

describe('when calling `forEach`', () => {
it('should call the callback with each subfield', () => {
const cb = jest.fn();

marcFieldContent.forEach(cb);

expect(cb).toHaveBeenCalledTimes(4);
expect(cb.mock.calls[0][0]).toEqual({ code: '$a', value: 'a1' });
expect(cb.mock.calls[1][0]).toEqual({ code: '$b', value: 'b1' });
expect(cb.mock.calls[2][0]).toEqual({ code: '$b', value: 'b2' });
expect(cb.mock.calls[3][0]).toEqual({ code: '$a', value: 'a2' });
});
});

describe('when calling `map`', () => {
it('should call the callback with each subfield', () => {
const cb = jest.fn();

marcFieldContent.map(cb);

expect(cb).toHaveBeenCalledTimes(4);
expect(cb.mock.calls[0][0]).toEqual({ code: '$a', value: 'a1' });
expect(cb.mock.calls[1][0]).toEqual({ code: '$b', value: 'b1' });
expect(cb.mock.calls[2][0]).toEqual({ code: '$b', value: 'b2' });
expect(cb.mock.calls[3][0]).toEqual({ code: '$a', value: 'a2' });
});
});

describe('when calling `reduce`', () => {
it('should call the callback with each subfield', () => {
const cb = jest.fn().mockImplementation((acc, cur) => [...acc, cur]);

marcFieldContent.reduce(cb, []);

expect(cb).toHaveBeenCalledTimes(4);
expect(cb.mock.calls[0][1]).toEqual({ code: '$a', value: 'a1' });
expect(cb.mock.calls[1][1]).toEqual({ code: '$b', value: 'b1' });
expect(cb.mock.calls[2][1]).toEqual({ code: '$b', value: 'b2' });
expect(cb.mock.calls[3][1]).toEqual({ code: '$a', value: 'a2' });
});
});

describe('when calling `toContentString`', () => {
it('should transform subfields array back to a string', () => {
expect(marcFieldContent.toContentString()).toEqual(content);
});
});

describe('when calling `append`', () => {
it('should add a new subfield to the end', () => {
marcFieldContent.append('$a', 'a3');

expect(marcFieldContent.toContentString()).toEqual(`${content} $a a3`);
});
});

describe('when calling `prepend`', () => {
it('should add a new subfield to the beginning', () => {
marcFieldContent.prepend('$a', 'a3');

expect(marcFieldContent.toContentString()).toEqual(`$a a3 ${content}`);
});
});

describe('when calling `removeByCode`', () => {
it('should remove all subfields by code', () => {
marcFieldContent.removeByCode('$a');

expect(marcFieldContent.toContentString()).toEqual('$b b1 $b b2');
});
});

describe('when calling `findAllByCode`', () => {
it('should return all subfields by code', () => {
expect(marcFieldContent.findAllByCode('$a')).toEqual([{ code: '$a', value: 'a1' }, { code: '$a', value: 'a2' }]);
});
});

describe('when using a subfield getter', () => {
it('should return an array of subfields values', () => {
expect(marcFieldContent.$a).toEqual(['a1', 'a2']);
});
});
});
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';
Loading

0 comments on commit ba58778

Please sign in to comment.