From cfad1f2a7ef6f4fc9da5dafbf2f64ded74f70ae2 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 4 Sep 2023 16:55:20 -0300 Subject: [PATCH 1/8] be able to indicate nested paths on __experimentalSaveSpecifiedEntityEdits --- packages/core-data/src/actions.js | 11 ++-- .../core-data/src/utils/get-nested-value.js | 20 ++++++ packages/core-data/src/utils/index.js | 1 + .../core-data/src/utils/set-nested-value.js | 18 ++++-- .../src/utils/test/get-nested-value.js | 61 +++++++++++++++++++ .../src/utils/test/set-nested-value.js | 7 +++ 6 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 packages/core-data/src/utils/get-nested-value.js create mode 100644 packages/core-data/src/utils/test/get-nested-value.js diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 1969d2cd717a2..b507bde8acab9 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -14,6 +14,7 @@ import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ +import { getNestedValue, setNestedValue } from './utils'; import { receiveItems, removeItems, receiveQueriedItems } from './queried-data'; import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities'; import { createBatch } from './batch'; @@ -779,7 +780,7 @@ export const saveEditedEntityRecord = * @param {string} kind Kind of the entity. * @param {string} name Name of the entity. * @param {Object} recordId ID of the record. - * @param {Array} itemsToSave List of entity properties to save. + * @param {Array} itemsToSave List of entity properties or properties paths to save. * @param {Object} options Saving options. */ export const __experimentalSaveSpecifiedEntityEdits = @@ -794,10 +795,9 @@ export const __experimentalSaveSpecifiedEntityEdits = recordId ); const editsToSave = {}; - for ( const edit in edits ) { - if ( itemsToSave.some( ( item ) => item === edit ) ) { - editsToSave[ edit ] = edits[ edit ]; - } + + for ( const item of itemsToSave ) { + setNestedValue( editsToSave, item, getNestedValue( edits, item ) ); } const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); @@ -814,7 +814,6 @@ export const __experimentalSaveSpecifiedEntityEdits = if ( recordId ) { editsToSave[ entityIdKey ] = recordId; } - return await dispatch.saveEntityRecord( kind, name, diff --git a/packages/core-data/src/utils/get-nested-value.js b/packages/core-data/src/utils/get-nested-value.js new file mode 100644 index 0000000000000..66198f8e0db95 --- /dev/null +++ b/packages/core-data/src/utils/get-nested-value.js @@ -0,0 +1,20 @@ +/** + * Helper util to return a value from a certain path of the object. + * Path is specified as either: + * - a string of properties, separated by dots, for example: "x.y". + * - an array of properties, for example `[ 'x', 'y' ]`. + * You can also specify a default value in case the result is nullish. + * + * @param {Object} object Input object. + * @param {string|Array} path Path to the object property. + * @param {*} defaultValue Default value if the value at the specified path is nullish. + * @return {*} Value of the object property at the specified path. + */ +export default function getNestedValue( object, path, defaultValue ) { + const normalizedPath = Array.isArray( path ) ? path : path.split( '.' ); + let value = object; + normalizedPath.forEach( ( fieldName ) => { + value = value?.[ fieldName ]; + } ); + return value !== undefined ? value : defaultValue; +} diff --git a/packages/core-data/src/utils/index.js b/packages/core-data/src/utils/index.js index 4f4149c8265b2..f37efe6eee7fd 100644 --- a/packages/core-data/src/utils/index.js +++ b/packages/core-data/src/utils/index.js @@ -7,3 +7,4 @@ export { default as replaceAction } from './replace-action'; export { default as withWeakMapCache } from './with-weak-map-cache'; export { default as isRawAttribute } from './is-raw-attribute'; export { default as setNestedValue } from './set-nested-value'; +export { default as getNestedValue } from './get-nested-value'; diff --git a/packages/core-data/src/utils/set-nested-value.js b/packages/core-data/src/utils/set-nested-value.js index e90bf23e4dad8..cb2ae788d1c92 100644 --- a/packages/core-data/src/utils/set-nested-value.js +++ b/packages/core-data/src/utils/set-nested-value.js @@ -4,6 +4,10 @@ * Arrays are created for missing index properties while objects are created * for all other missing properties. * + * Path is specified as either: + * - a string of properties, separated by dots, for example: "x.y". + * - an array of properties, for example `[ 'x', 'y' ]`. + * * This function intentionally mutates the input object. * * Inspired by _.set(). @@ -12,24 +16,26 @@ * * @todo Needs to be deduplicated with its copy in `@wordpress/edit-site`. * - * @param {Object} object Object to modify - * @param {Array} path Path of the property to set. - * @param {*} value Value to set. + * @param {Object} object Object to modify + * @param {Array|string} path Path of the property to set. + * @param {*} value Value to set. */ export default function setNestedValue( object, path, value ) { if ( ! object || typeof object !== 'object' ) { return object; } - path.reduce( ( acc, key, idx ) => { + const normalizedPath = Array.isArray( path ) ? path : path.split( '.' ); + + normalizedPath.reduce( ( acc, key, idx ) => { if ( acc[ key ] === undefined ) { - if ( Number.isInteger( path[ idx + 1 ] ) ) { + if ( Number.isInteger( normalizedPath[ idx + 1 ] ) ) { acc[ key ] = []; } else { acc[ key ] = {}; } } - if ( idx === path.length - 1 ) { + if ( idx === normalizedPath.length - 1 ) { acc[ key ] = value; } return acc[ key ]; diff --git a/packages/core-data/src/utils/test/get-nested-value.js b/packages/core-data/src/utils/test/get-nested-value.js new file mode 100644 index 0000000000000..4c1ae68317a6a --- /dev/null +++ b/packages/core-data/src/utils/test/get-nested-value.js @@ -0,0 +1,61 @@ +/** + * Internal dependencies + */ +import getNestedValue from '../get-nested-value'; + +describe( 'setNestedValue', () => { + it( 'should return the same object unmodified if path is an empty array', () => { + const input = { x: 'y' }; + const result = getNestedValue( input, [] ); + expect( result ).toEqual( input ); + } ); + + it( 'should the nested value', () => { + const input = { x: { y: { z: 123 } } }; + const result = getNestedValue( input, [ 'x', 'y', 'z' ] ); + + expect( result ).toEqual( 123 ); + } ); + + it( 'should return the nested value if the path is a string', () => { + const input = { x: { y: { z: 123 } } }; + const result = getNestedValue( input, 'x.y.z' ); + + expect( result ).toEqual( 123 ); + } ); + + it( 'should return the shallow value', () => { + const input = { x: { y: { z: 123 } } }; + const result = getNestedValue( input, 'x' ); + + expect( result ).toEqual( { y: { z: 123 } } ); + } ); + + it( 'should return the default value if the nested value is undefined', () => { + const input = { x: { y: { z: undefined } } }; + const result = getNestedValue( input, [ 'x', 'y', 'z' ], 456 ); + + expect( result ).toEqual( 456 ); + } ); + + it( 'should return the nested value if it different to undefined', () => { + const input = { x: { y: { z: null } } }; + const result = getNestedValue( input, 'x.y.z', 456 ); + + expect( result ).toBeNull(); + } ); + + it( 'should return the default value if the nested value does not exist', () => { + const input = { x: { y: { z: 123 } } }; + const result = getNestedValue( input, [ 'x', 'y', 'z1' ], 456 ); + + expect( result ).toEqual( 456 ); + } ); + + it( 'should return undefined if the nested value does not exist', () => { + const input = { x: { y: { z: 123 } } }; + const result = getNestedValue( input, [ 'x', 'y', 'z1' ] ); + + expect( result ).toBeUndefined(); + } ); +} ); diff --git a/packages/core-data/src/utils/test/set-nested-value.js b/packages/core-data/src/utils/test/set-nested-value.js index bbc7129180764..7785e94830109 100644 --- a/packages/core-data/src/utils/test/set-nested-value.js +++ b/packages/core-data/src/utils/test/set-nested-value.js @@ -19,6 +19,13 @@ describe( 'setNestedValue', () => { expect( result ).toEqual( { x: { y: { z: 456 } } } ); } ); + it( 'should set values at deep level having a string as path', () => { + const input = { x: { y: { z: 123 } } }; + const result = setNestedValue( input, 'x.y.z', 456 ); + + expect( result ).toEqual( { x: { y: { z: 456 } } } ); + } ); + it( 'should create nested objects if necessary', () => { const result = setNestedValue( {}, [ 'x', 'y', 'z' ], 123 ); From f05d3c500414643863df9a822ef5d6a43a03613c Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 4 Sep 2023 17:37:18 -0300 Subject: [PATCH 2/8] fix test decription --- packages/core-data/src/utils/test/get-nested-value.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/src/utils/test/get-nested-value.js b/packages/core-data/src/utils/test/get-nested-value.js index 4c1ae68317a6a..29417f7d496b9 100644 --- a/packages/core-data/src/utils/test/get-nested-value.js +++ b/packages/core-data/src/utils/test/get-nested-value.js @@ -3,7 +3,7 @@ */ import getNestedValue from '../get-nested-value'; -describe( 'setNestedValue', () => { +describe( 'getNestedValue', () => { it( 'should return the same object unmodified if path is an empty array', () => { const input = { x: 'y' }; const result = getNestedValue( input, [] ); From 00cf84d6bfdace1c3c210ef61dad833ac5b81657 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Tue, 5 Sep 2023 17:08:03 -0300 Subject: [PATCH 3/8] fix test description text Co-authored-by: Jeff Ong --- packages/core-data/src/utils/test/get-nested-value.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/src/utils/test/get-nested-value.js b/packages/core-data/src/utils/test/get-nested-value.js index 29417f7d496b9..7a03fbf5738db 100644 --- a/packages/core-data/src/utils/test/get-nested-value.js +++ b/packages/core-data/src/utils/test/get-nested-value.js @@ -38,7 +38,7 @@ describe( 'getNestedValue', () => { expect( result ).toEqual( 456 ); } ); - it( 'should return the nested value if it different to undefined', () => { + it( 'should return the nested value if it is different to undefined', () => { const input = { x: { y: { z: null } } }; const result = getNestedValue( input, 'x.y.z', 456 ); From 865560a45ab922e5434072eaf2c5c03260f6bf4b Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Tue, 5 Sep 2023 17:55:20 -0300 Subject: [PATCH 4/8] adding type checking Co-authored-by: Jeff Ong --- packages/core-data/src/utils/get-nested-value.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core-data/src/utils/get-nested-value.js b/packages/core-data/src/utils/get-nested-value.js index 66198f8e0db95..c55bdde8e37b3 100644 --- a/packages/core-data/src/utils/get-nested-value.js +++ b/packages/core-data/src/utils/get-nested-value.js @@ -11,6 +11,9 @@ * @return {*} Value of the object property at the specified path. */ export default function getNestedValue( object, path, defaultValue ) { + if ( ( ! object || typeof object !== 'object' ) || (typeof path !== 'string' && !Array.isArray(path)) ) { + return object; + } const normalizedPath = Array.isArray( path ) ? path : path.split( '.' ); let value = object; normalizedPath.forEach( ( fieldName ) => { From 8ce14a39a2ca8c047e16f894df289bb4dd0a2887 Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Wed, 6 Sep 2023 10:12:17 +0100 Subject: [PATCH 5/8] Format if statement --- packages/core-data/src/utils/get-nested-value.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core-data/src/utils/get-nested-value.js b/packages/core-data/src/utils/get-nested-value.js index c55bdde8e37b3..eaf8eb7b511a9 100644 --- a/packages/core-data/src/utils/get-nested-value.js +++ b/packages/core-data/src/utils/get-nested-value.js @@ -11,7 +11,11 @@ * @return {*} Value of the object property at the specified path. */ export default function getNestedValue( object, path, defaultValue ) { - if ( ( ! object || typeof object !== 'object' ) || (typeof path !== 'string' && !Array.isArray(path)) ) { + if ( + ! object || + typeof object !== 'object' || + ( typeof path !== 'string' && ! Array.isArray( path ) ) + ) { return object; } const normalizedPath = Array.isArray( path ) ? path : path.split( '.' ); From 656ccf000a16798b3acd4d5f2ba5ff123ed43643 Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Thu, 7 Sep 2023 15:27:47 +0100 Subject: [PATCH 6/8] Tweak comment wording --- packages/core-data/src/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index b507bde8acab9..714163902b2d7 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -780,7 +780,7 @@ export const saveEditedEntityRecord = * @param {string} kind Kind of the entity. * @param {string} name Name of the entity. * @param {Object} recordId ID of the record. - * @param {Array} itemsToSave List of entity properties or properties paths to save. + * @param {Array} itemsToSave List of entity properties or property paths to save. * @param {Object} options Saving options. */ export const __experimentalSaveSpecifiedEntityEdits = From b3ade9acf5327706fd09ce520e6acdbc840bf495 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 11 Sep 2023 10:46:06 -0300 Subject: [PATCH 7/8] update description Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com> --- packages/core-data/src/utils/test/get-nested-value.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/src/utils/test/get-nested-value.js b/packages/core-data/src/utils/test/get-nested-value.js index 7a03fbf5738db..1048885d39c67 100644 --- a/packages/core-data/src/utils/test/get-nested-value.js +++ b/packages/core-data/src/utils/test/get-nested-value.js @@ -10,7 +10,7 @@ describe( 'getNestedValue', () => { expect( result ).toEqual( input ); } ); - it( 'should the nested value', () => { + it( 'should return the nested value', () => { const input = { x: { y: { z: 123 } } }; const result = getNestedValue( input, [ 'x', 'y', 'z' ] ); From de832bf14a6789cdf0bec15c5117799b14622b7c Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Tue, 12 Sep 2023 13:51:28 -0300 Subject: [PATCH 8/8] correct function doc --- packages/core-data/src/utils/get-nested-value.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/src/utils/get-nested-value.js b/packages/core-data/src/utils/get-nested-value.js index eaf8eb7b511a9..0fe210159b07b 100644 --- a/packages/core-data/src/utils/get-nested-value.js +++ b/packages/core-data/src/utils/get-nested-value.js @@ -7,7 +7,7 @@ * * @param {Object} object Input object. * @param {string|Array} path Path to the object property. - * @param {*} defaultValue Default value if the value at the specified path is nullish. + * @param {*} defaultValue Default value if the value at the specified path is undefined. * @return {*} Value of the object property at the specified path. */ export default function getNestedValue( object, path, defaultValue ) {