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

Lodash: Refactor away from _.unionBy() #43735

Merged
merged 2 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all 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 .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ module.exports = {
'toString',
'trim',
'truncate',
'unionBy',
'uniq',
'uniqBy',
'uniqueId',
Expand Down
4 changes: 4 additions & 0 deletions packages/block-library/src/block/test/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ describe( 'Reusable block', () => {
response = [ reusableBlockMock1, reusableBlockMock2 ];
} else if ( path.startsWith( '/wp/v2/blocks/1' ) ) {
response = reusableBlockMock1;
} else if (
path.startsWith( '/wp/v2/block-patterns/categories' )
) {
response = [];
}
return Promise.resolve( response );
} );
Expand Down
17 changes: 17 additions & 0 deletions packages/block-library/src/embed/test/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ const mockEmbedResponses = ( mockedResponses ) => {
] );
}

if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) {
return Promise.resolve( [] );
}

const matchedEmbedResponse = mockedResponses.find(
( mockedResponse ) =>
path ===
Expand Down Expand Up @@ -698,6 +702,9 @@ describe( 'Embed block', () => {
response = RICH_TEXT_EMBED_SUCCESS_RESPONSE;
}
}
if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) {
response = [];
}
return Promise.resolve( response );
} );

Expand All @@ -724,6 +731,9 @@ describe( 'Embed block', () => {
it( 'converts to link if preview request failed', async () => {
// Return bad response for requests to oembed endpoint.
fetchRequest.mockImplementation( ( { path } ) => {
if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) {
return Promise.resolve( [] );
}
const isEmbedRequest = path.startsWith( '/oembed/1.0/proxy' );
return Promise.resolve(
isEmbedRequest ? MOCK_BAD_WORDPRESS_RESPONSE : {}
Expand Down Expand Up @@ -761,6 +771,10 @@ describe( 'Embed block', () => {
response = MOCK_BAD_WORDPRESS_RESPONSE;
} else if ( matchesPath( successURL ) ) {
response = RICH_TEXT_EMBED_SUCCESS_RESPONSE;
} else if (
path.startsWith( '/wp/v2/block-patterns/categories' )
) {
response = [];
}

return Promise.resolve( response );
Expand Down Expand Up @@ -1048,6 +1062,9 @@ describe( 'Embed block', () => {
it( 'displays cannot embed on the placeholder if preview data is null', async () => {
// Return null response for requests to oembed endpoint.
fetchRequest.mockImplementation( ( { path } ) => {
if ( path.startsWith( '/wp/v2/block-patterns/categories' ) ) {
return Promise.resolve( [] );
}
const isEmbedRequest = path.startsWith( '/oembed/1.0/proxy' );
return Promise.resolve( isEmbedRequest ? EMBED_NULL_RESPONSE : {} );
} );
Expand Down
20 changes: 14 additions & 6 deletions packages/edit-site/src/components/block-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* External dependencies
*/
import classnames from 'classnames';
import { unionBy } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -80,16 +79,25 @@ export default function BlockEditor( { setIsInserterOpen } ) {
);

const blockPatterns = useMemo(
() => unionBy( settingsBlockPatterns, restBlockPatterns, 'name' ),
() =>
[
...( settingsBlockPatterns || [] ),
...( restBlockPatterns || [] ),
].filter(
( x, index, arr ) =>
index === arr.findIndex( ( y ) => x.name === y.name )
),
Comment on lines +82 to +89
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is simple from an implementation perspective, but it is O(n^2)...

Given that we have 4 uses of this in the same package, perhaps it would be worth having a utility method instead, that checked for uniqueness on insert? It should be sub-quadratic (O(n log n), I believe) if it keeps the keys in an auxiliary Set and checks that for each item in the arrays upon insertion.

What do you think, @tyxla?

Copy link
Member Author

@tyxla tyxla Aug 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't come up with a helper method because this is actually in 2 separate packages - @wordpress/edit-site and @wordpress/editor. So if we had a helper method, we'd have to either move it somewhere and export it - something I'm consciously avoiding with this migration - or, repeat it, which is never cool, especially if the method is more than a couple of lines. Seems like a check on insertion would be a bit more complex than what we came up with here. A simplified version of it would be something like:

const result = [ ... settingsBlockPatterns ];

restBlockPatterns.forEach( ( value ) => {
	if( ! result.some( e => e.name === value.name ) ) {
		result.push( value );
	}
} );

That being said, I'm open to suggestions for improving the implementation - do you have anything specific in mind.

Copy link
Contributor

@sgomes sgomes Aug 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the fact that this is two different packages, you're right 🤔

Your new suggestion is still O(n^2), however.

The simplest functional approach with sub-quadratic complexity I can come up with is:

restBlockPatterns.reduce(
  ( acc, pattern ) => {
    const { names, patterns } = acc;
    if ( ! names.has( pattern.name ) ) {
      names.add( pattern.name );
      patterns.push( pattern );
    }
    return acc;
  },
  { 
    names: new Set( settingsBlockPatterns.map( p => p.name ) ),
    patterns: [ ...settingsBlockPatterns ],
  }
).patterns;

This will be O(n log n) or O(n), depending on whether Set.prototype.has is O(log n) or O(1).

That said, it is far more involved than your original approach, so I'll leave it to your judgment. Realistically, this is probably not going to be a hot path, so code simplicity probably wins over algorithmic complexity concerns here 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you expressed my thoughts here: we can make this faster, but it's not worth the reduction of readability, so I prefer to go with the simplicity. Not to mention that the unionBy() original function appears to have quite the complexity (it's pretty complex), but I haven't spent time going through it and calculating it.

[ settingsBlockPatterns, restBlockPatterns ]
);

const blockPatternCategories = useMemo(
() =>
unionBy(
settingsBlockPatternCategories,
restBlockPatternCategories,
'name'
[
...( settingsBlockPatternCategories || [] ),
...( restBlockPatternCategories || [] ),
].filter(
( x, index, arr ) =>
index === arr.findIndex( ( y ) => x.name === y.name )
),
[ settingsBlockPatternCategories, restBlockPatternCategories ]
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { pick, unionBy } from 'lodash';
import { pick } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -77,16 +77,25 @@ function useBlockEditorSettings( settings, hasTemplate ) {
);

const blockPatterns = useMemo(
() => unionBy( settingsBlockPatterns, restBlockPatterns, 'name' ),
() =>
[
...( settingsBlockPatterns || [] ),
...( restBlockPatterns || [] ),
].filter(
( x, index, arr ) =>
index === arr.findIndex( ( y ) => x.name === y.name )
),
[ settingsBlockPatterns, restBlockPatterns ]
);

const blockPatternCategories = useMemo(
() =>
unionBy(
settingsBlockPatternCategories,
restBlockPatternCategories,
'name'
[
...( settingsBlockPatternCategories || [] ),
...( restBlockPatternCategories || [] ),
].filter(
( x, index, arr ) =>
index === arr.findIndex( ( y ) => x.name === y.name )
),
[ settingsBlockPatternCategories, restBlockPatternCategories ]
);
Expand Down