Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UIQM-724 do not group together subfields during linking #750

Merged
merged 4 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 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);
}

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];
}
}
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 `join`', () => {
it('should transform subfields array back to a string', () => {
expect(marcFieldContent.join()).toEqual(content);
});
});

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

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

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

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

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

expect(marcFieldContent.join()).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