Skip to content

Commit

Permalink
UISACQCOMP-143 Add the ability to get a list of all nested field name…
Browse files Browse the repository at this point in the history
…s when comparing versions (#675)

* UISACQCOMP-143 Add the ability to get a list of all nested field names when comparing versions

* UISACQCOMP-143 update tests

* UISACQCOMP-143 resolve cycle dependency issue

* UISACQCOMP-143 add unit test

* UISACQCOMP-143 update unit test
  • Loading branch information
usavkov-epam authored Feb 15, 2023
1 parent 766df0c commit 6366d2c
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* *BREAKING*: Upgrade `react-redux` to `v8`. Refs UISACQCOMP-137.
* Do not display version history cards without changes. Refs UISACQCOMP-139.
* *BREAKING*: Update `@folio/stripes` to `8.0.0`. Refs UISACQCOMP-140.
* Add the ability to get a list of all nested field names when comparing versions. Refs UISACQCOMP-143.

## [3.3.2](https://github.com/folio-org/stripes-acq-components/tree/v3.3.2) (2022-11-25)
[Full Changelog](https://github.com/folio-org/stripes-acq-components/compare/v3.3.1...v3.3.2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ jest.mock('../../hooks', () => ({
const TEST_ID = 'testId';

const poLineLabelsMap = {
'fundDistribution': 'ui-orders.line.accordion.fund',
'fundDistribution[\\d]': 'ui-orders.line.accordion.fund',
'fundDistribution[\\d].fundId': 'stripes-acq-components.fundDistribution.name',
'fundDistribution[\\d].code': 'stripes-acq-components.fundDistribution.name',
'fundDistribution[\\d].expenseClassId': 'stripes-acq-components.fundDistribution.expenseClass',
Expand Down Expand Up @@ -97,7 +95,6 @@ describe('VersionHistoryPane', () => {
// Changed fields
expect(screen.getByText('stripes-acq-components.versionHistory.card.changed')).toBeInTheDocument();
expect(screen.getByText('stripes-acq-components.fundDistribution.value')).toBeInTheDocument();
expect(screen.getByText('ui-orders.line.accordion.fund')).toBeInTheDocument();
});

it('should not display version cards without changed fields', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { createContext, useMemo } from 'react';
import { get } from 'lodash';

import { useVersionsDifference } from '../hooks';
import { useVersionsDifference } from '../hooks/useVersionsDifference';

export const VersionViewContext = createContext();

Expand Down
9 changes: 2 additions & 7 deletions lib/VersionHistory/getVersionWrappedFormatter.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { getHighlightedFields } from './getHighlightedFields';

export const getVersionWrappedFormatter = ({
baseFormatter,
changes,
fieldsMapping,
name,
paths,
}) => {
const formatterEntries = Object.entries(baseFormatter);
const fieldNames = Object.values(fieldsMapping);

const highlights = getHighlightedFields({ changes, fieldNames, name });

return formatterEntries.reduce((acc, [colName, renderCell]) => {
return {
...acc,
[colName]: ({ rowIndex, ...rest }) => {
const isUpdated = highlights.includes(`${name}[${rowIndex}].${fieldsMapping[colName]}`);
const isUpdated = paths?.includes(`${name}[${rowIndex}].${fieldsMapping[colName]}`);

const content = renderCell({ rowIndex, ...rest });

Expand Down
6 changes: 4 additions & 2 deletions lib/VersionHistory/getVersionWrappedFormatter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { getVersionWrappedFormatter } from './getVersionWrappedFormatter';

const changes = [
{ type: FIELD_CHANGE_TYPES.update, path: 'fieldOne[0].name' },
{ type: FIELD_CHANGE_TYPES.create, path: 'fieldOne[1]' },
{ type: FIELD_CHANGE_TYPES.create, path: 'fieldOne[1].name' },
{ type: FIELD_CHANGE_TYPES.update, path: 'field.two[0].foo' },
];

const paths = changes.map(({ path }) => path);

const COLUMNS = { foo: 'foo' };
const baseFormatter = { [COLUMNS.foo]: jest.fn(({ name }) => name) };
const columnMapping = { [COLUMNS.foo]: 'Column head text' };
Expand All @@ -37,7 +39,7 @@ describe('getVersionWrappedFormatter', () => {
const name = 'fieldOne';
const formatter = getVersionWrappedFormatter({
baseFormatter,
changes,
paths,
fieldsMapping,
name,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const useVersionWrappedFormatter = ({ baseFormatter, name, fieldsMapping
baseFormatter,
fieldsMapping,
name,
changes: versionContext.changes,
paths: versionContext.paths,
});
}, [baseFormatter, fieldsMapping, name, versionContext]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import { get, uniqBy } from 'lodash';
import { get, isObject, uniqBy } from 'lodash';
import { useMemo } from 'react';

import { objectDifference } from '../../../utils';
import {
FIELD_CHANGE_TYPES,
finalFormPathBuilder,
getObjectKey,
objectDifference,
} from '../../../utils';

const getNestedObjectKeys = (path, value) => {
if (!isObject(value)) return [path];

return Object
.entries(value)
.flatMap(([key, val]) => getNestedObjectKeys(
finalFormPathBuilder([path, getObjectKey(val, key)]),
val,
));
};

const getNestedFieldPaths = (type, path, [oldValue, newValue]) => {
const targetValue = type === FIELD_CHANGE_TYPES.delete ? oldValue : newValue;

return getNestedObjectKeys(path, targetValue);
};

export const useVersionsDifference = (auditEvents, snapshotPath) => {
const users = useMemo(() => uniqBy(auditEvents.map(({ userId, username }) => ({ id: userId, username })), 'id'), [auditEvents]);
Expand All @@ -11,15 +33,21 @@ export const useVersionsDifference = (auditEvents, snapshotPath) => {

acc[event.id] = (i === (auditEvents.length - 1))
? null
: objectDifference(get(auditEvents[i + 1], snapshotPath, {}), snapshot).reduce((accum, item) => {
accum.changes.push(item);
accum.paths.push(item.path);

return accum;
}, {
changes: [],
paths: [],
});
: objectDifference(get(auditEvents[i + 1], snapshotPath, {}), snapshot)
.reduce((accum, item) => {
const { path, type, values } = item;
const paths = type === FIELD_CHANGE_TYPES.update
? [path]
: getNestedFieldPaths(type, path, values);

accum.changes.push(item);
accum.paths.push(...paths);

return accum;
}, {
changes: [],
paths: [],
});

return acc;
}, {}), [auditEvents, snapshotPath]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,38 @@ describe('useVersionsDifference', () => {
},
});
});

it('should return paths for all created or deleted fields with nesting', () => {
const event = {
id: 'event-id',
username: 'testuser',
snapshot: {
id: 'test-id',
notNestingField: 'Hello',
fieldWithNesting: {
first: 'first field',
second: 'second field',
},
},
};

const clonedEvent = Object.assign(cloneDeep(event), { id: 'clonedEventId', username: 'testuser' });

set(clonedEvent.snapshot, 'fanotherFeldWithNesting', { foo: 'bar' });
set(clonedEvent.snapshot, 'notNestingField', 'value');
unset(clonedEvent.snapshot, 'fieldWithNesting');

const { result } = renderHook(() => useVersionsDifference([clonedEvent, event], 'snapshot'));

const { versionsMap } = result.current;

expect(versionsMap[clonedEvent.id]).toEqual(expect.objectContaining({
paths: expect.arrayContaining([
'notNestingField',
'fanotherFeldWithNesting.foo', // added in the object
'fieldWithNesting.first', // removed from the object
'fieldWithNesting.second', // removed from the object
]),
}));
});
});
7 changes: 6 additions & 1 deletion lib/utils/objectDifference/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export { FIELD_CHANGE_TYPES, objectDifference } from './objectDifference';
export {
FIELD_CHANGE_TYPES,
finalFormPathBuilder,
getObjectKey,
objectDifference,
} from './objectDifference';
3 changes: 2 additions & 1 deletion lib/utils/objectDifference/objectDifference.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
} from 'lodash';

const isEmptyObject = (value) => isObject(value) && isEmpty(value);
const getObjectKey = (obj, key) => (

export const getObjectKey = (obj, key) => (
Array.isArray(obj) && Number.isInteger(+key)
? Number(key)
: key
Expand Down

0 comments on commit 6366d2c

Please sign in to comment.