Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Single Product Block #2399

Closed
wants to merge 35 commits into from
Closed

Single Product Block #2399

wants to merge 35 commits into from

Conversation

mikejolley
Copy link
Member

@mikejolley mikejolley commented May 4, 2020

Creates the basic structure for the Single Product Block (behind the experimental flag).

Closes #2392
Closes #2535

  • Creates the block, with icon and description
  • Implements the product picker in edit mode and the inspector
  • Moves the inner block layout functionality to shared atomic utils (used by all products and single product blocks)
    • utils also include block->component maps and anything else related to atomic components
  • Introduces a new hook named useSyncedLayoutConfig which syncs inner block changes to block attributes as a LayoutConfig array. This is only being used by Single Products Block currently. The All Products block does not update instantly and so does not need this (yet)
  • Introduces a new method of rendering inner blocks without layout configs. ([Experiment] Alternative layout rendering #2542)
  • Removes dedicated atomic/components dir so that the components can share attribute data with atomic/blocks when rendering on the frontend.
  • Combines useProductLayoutContext and useInnerBlockConfigurationContext since both are used at the same time in all contexts. This allows the parent block to be passed, as well as a prefix for the CSS classnames.
  • Introduces ProductDataContext to share real product data with atomic blocks.
    • For all products block, this is where preview data is passed. Prior to this change, the atomic blocks themselves looked up preview data.
    • For the single product block, it passes real product data via this context.
    • Atomic blocks feed this data to the atomic components
  • Updates atomic blocks to have proper loading states and consume ProductDataContext.
    • Prior to this some of the atomic blocks had loading states, but others did not. I've improved this so anytime product is null, it's considered in loading state. This is useful if the product data is being pulled from the API.
  • Shared contexts so Atomic blocks are functional (see Single Product Block #2399 (comment)) This ensures contexts work when wrapping atomic blocks in the editor and on frontend.

Known follow ups:

Screenshots

Edit interface:
Screenshot 2020-05-06 at 10 14 14
Screenshot 2020-05-06 at 10 15 26

Loading state:
placeholder

Inner Block state:
content

How to test

  1. Confirm All Products Block remains functional in editor/frontend
  2. In development mode, confirm the block is built and shown in the editor
  3. Insert the block and select a product. Ensure it persists on save.
  4. Try toggling between edit and layout mode.
  5. Confirm placeholder details are shown on frontend.

@mikejolley mikejolley added the type: new block Applied to work that introduces a new block (typically used on an epic issue). label May 4, 2020
@mikejolley mikejolley self-assigned this May 4, 2020
@github-actions
Copy link
Contributor

github-actions bot commented May 4, 2020

Size Change: +131 kB (6%) 🔍

Total Size: 2.13 MB

Filename Size Change
build/active-filters-frontend.js 13.6 kB +6.34 kB (46%) 🚨
build/active-filters.js 7.88 kB -7 B (0%)
build/all-products-frontend.js 78.3 kB +6.53 kB (8%) 🔍
build/all-products.js 16.5 kB -2.65 kB (16%) 👏
build/all-reviews.js 10.6 kB -8 B (0%)
build/attribute-filter-frontend.js 23 kB +6.34 kB (27%) 🚨
build/attribute-filter.js 11.5 kB -3 B (0%)
build/block-error-boundary.js 774 B -1 B
build/blocks.js 2.92 kB -2 B (0%)
build/cart-frontend.js 119 kB +6.04 kB (5%) 🔍
build/cart.js 32.3 kB +61 B (0%)
build/checkout-frontend.js 135 kB +5.93 kB (4%)
build/checkout.js 37.9 kB +58 B (0%)
build/editor-rtl.css 13.5 kB +25 B (0%)
build/editor.css 13.5 kB +24 B (0%)
build/featured-category.js 146 kB +151 B (0%)
build/featured-product.js 9.79 kB -22 B (0%)
build/handpicked-products.js 7.15 kB +3 B (0%)
build/price-filter-frontend.js 20.5 kB +6.39 kB (31%) 🚨
build/price-filter.js 9.98 kB +4 B (0%)
build/product-best-sellers.js 7.22 kB -7 B (0%)
build/product-categories.js 3.21 kB -1 B
build/product-category.js 8.14 kB +2 B (0%)
build/product-new.js 7.38 kB -7 B (0%)
build/product-on-sale.js 7.78 kB -9 B (0%)
build/product-search.js 3.37 kB +1 B
build/product-tag.js 6.33 kB +2 B (0%)
build/product-top-rated.js 7.36 kB -6 B (0%)
build/products-by-attribute.js 8.11 kB -3 B (0%)
build/reviews-by-category.js 12.7 kB -10 B (0%)
build/reviews-by-product.js 14.1 kB -8 B (0%)
build/reviews-frontend-legacy.js 13.8 kB +5.81 kB (42%) 🚨
build/reviews-frontend.js 15.3 kB +6.49 kB (42%) 🚨
build/spinner-style.js 773 B +2 B (0%)
build/style-legacy-rtl.css 5.63 kB +7 B (0%)
build/style-legacy.css 5.63 kB +7 B (0%)
build/style-rtl.css 17.3 kB +777 B (4%)
build/style.css 17.3 kB +783 B (4%)
build/vendors-style.js 107 B +2 B (1%)
build/vendors.js 479 kB +5.83 kB (1%)
build/wc-blocks-data.js 7.08 kB +5 B (0%)
build/wc-blocks-middleware.js 932 B +1 B
build/wc-blocks-registry.js 1.78 kB -3 B (0%)
build/wc-payment-method-cheque.js 0 B -794 B (0%)
build/wc-payment-method-paypal.js 0 B -830 B (0%)
build/wc-payment-method-stripe.js 0 B -11.9 kB (0%)
build/wc-settings.js 2.14 kB +1 B
build/single-product-frontend.js 74.7 kB +74.7 kB (100%) 🆘
build/single-product.js 14.1 kB +14.1 kB (100%) 🆘
build/wc-shared-context.js 1.18 kB +1.18 kB (100%) 🆘
ℹ️ View Unchanged
Filename Size Change
build/all-reviews-legacy.js 10.3 kB 0 B
build/block-error-boundary-legacy.js 774 B 0 B
build/blocks-legacy.js 2.92 kB 0 B
build/custom-select-control-style-legacy.js 782 B 0 B
build/custom-select-control-style.js 782 B 0 B
build/editor-legacy-rtl.css 12.5 kB 0 B
build/editor-legacy.css 12.6 kB 0 B
build/featured-category-legacy.js 146 kB 0 B
build/featured-product-legacy.js 9.49 kB 0 B
build/handpicked-products-legacy.js 6.88 kB 0 B
build/panel-style-legacy.js 773 B 0 B
build/panel-style.js 773 B 0 B
build/product-best-sellers-legacy.js 6.96 kB 0 B
build/product-categories-legacy.js 3.22 kB 0 B
build/product-category-legacy.js 7.88 kB 0 B
build/product-list-style-legacy.js 775 B 0 B
build/product-new-legacy.js 7.12 kB 0 B
build/product-on-sale-legacy.js 7.49 kB 0 B
build/product-search-legacy.js 3.12 kB 0 B
build/product-tag-legacy.js 6.07 kB 0 B
build/product-top-rated-legacy.js 7.09 kB 0 B
build/products-by-attribute-legacy.js 7.85 kB 0 B
build/reviews-by-category-legacy.js 12.3 kB 0 B
build/reviews-by-product-legacy.js 13.8 kB 0 B
build/snackbar-notice-style-legacy.js 778 B 0 B
build/snackbar-notice-style.js 778 B 0 B
build/spinner-style-legacy.js 775 B 0 B
build/vendors-legacy.js 376 kB 0 B
build/vendors-style-legacy-rtl.css 1.65 kB 0 B
build/vendors-style-legacy.css 1.65 kB 0 B
build/vendors-style-legacy.js 105 B 0 B
build/vendors-style-rtl.css 1.65 kB 0 B
build/vendors-style.css 1.65 kB 0 B

compressed-size-action

@nerrad
Copy link
Contributor

nerrad commented May 4, 2020

@mikejolley how would you feel about putting the bare minimum in place for this block and merging in behind a feature flag? That way we can avoid a long running development branch and keep pull requests relatively lean?

@mikejolley
Copy link
Member Author

@nerrad given the amount of focuses this cycle I want to keep PR reviews down; so one review/test of the block itself. Supporting changes (e.g. API) I will raise separately and rebase.

There isn't much point in putting an incomplete block behind a flag imo. I think those have more value for beta testing?

@nerrad
Copy link
Contributor

nerrad commented May 4, 2020

There isn't much point in putting an incomplete block behind a flag imo. I think those have more value for beta testing?

There's a few reasons why I prefer the feature flag approach:

  • Merging is less of a problem. If you have a long running separate pull, with various branches off of it for in progress development work, not only do you have to potentially deal with conflicts between those branches but also potential conflicts coming from master into the main development branch. This juggling between branches increases the potential for bad merge artifacts or merging in the incorrect order.
  • We have tooling created to help gauge impact of changes. In most cases, this tooling (particularly bundle size) only works at a single branch level off of master. We lose the insight of that tooling if we use a base branch for the feature (only seeing the impact of a pull when it's merged back into the feature branch). This is particularly problematic when these tools require on a clean history (via rebasing) which is harder to do with the feature branch approach.
  • The flag isn't really for testing. It's created so we have a way to have in progress work in master without worry of releasing it prematurely to users either in the wp.org channel or in the woo core. It only impacts release builds so our development happens without issues.
  • Having multiple focuses actually is a reason why I'd want this merged sooner because otherwise there's less visibility into the potential issues new components or changes in the feature branch might have with other in progress work.
  • Creating new components or improving existing ones can be done with less of a hassle because while you can still do that off master and then merge up into the feature branch - if you have other branches off of the feature brach there's an extra step involved.

About the only benefit I see for having a feature branch is to keep that work isolated from the rest of the code and prevent prematurely exposing to users. I think the latter is solved nicely by the feature flag and with the former I don't think that's something we need to worry about (and actually I'd sooner know about impacts to other existing code earlier in the development cycle rather than later after we merge the whole caboodle sometime down the road.

I'm definitely all ears 👂 if you have some reasons for keeping things in a feature branch that I haven't considered?

@mikejolley
Copy link
Member Author

mikejolley commented May 4, 2020

Merging is less of a problem. If you have a long running separate pull, with various branches off of it for in progress development work

We have tooling created to help gauge impact of changes. In most cases, this tooling (particularly bundle size) only works at a single branch level off of master

Having multiple focuses actually is a reason why I'd want this merged sooner because otherwise there's less visibility into the potential issues new components or changes in the feature branch might have with other in progress work.

I'm not doing that. I'm using this branch to get the block working and to a testable, reviewable state. I'm happy to put it behind a flag at that point, but not before. I mentioned the focuses because I know everyone is busy, so I'd prefer to have testable, full featured PRs rather than smaller, but harder to test without context, PRs that potentially block further work.

Creating new components or improving existing ones can be done with less of a hassle because while you can still do that off master and then merge up into the feature branch - if you have other branches off of the feature brach there's an extra step involved.

Any API changes or non-block specific components will be raised on master separately, and then rebased here once merged. I am not creating additional branches for review off of this branch.

I imagine I'll change this to a proper PR and request review when it satisfies #2392

I mainly created a draft to track progress (this PR is marked draft/in-progress), and so something is visible whilst work on components is done at the same time in other PRs.

The flag isn't really for testing. It's created so we have a way to have in progress work in master without worry of releasing it prematurely to users either in the wp.org channel or in the woo core.

I don't think we need to discuss this here and now, but we should really have 3 flags IMO. development, feature plugin, and core fwiw. If current flag usage is documented somewhere let me know so I can read up on it's proper purpose.

Obviously creating this draft has caused some confusion, so I can close it and raise again when I'm ready if you like? I didn't intend to kick off a large workflow debate.

@nerrad
Copy link
Contributor

nerrad commented May 4, 2020

My apologies, I'm pretty sure I misunderstood the purpose of this pull then :) I thought you were intending on using this for a long running feature branch (and I also missed the important indicator that this was a draft pull) 🤦

@mikejolley mikejolley force-pushed the add/single-product-block branch from ac263dd to 56a7277 Compare May 12, 2020 14:39
@github-actions
Copy link
Contributor

Add preview state for single product block

Add preview state for single product block


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/f88c31cb6a03c0b488485738dca276b45b98bda0/assets/js/blocks/single-product/edit.js#L85-L96

🚀 This comment was generated by the automations bot based on a todo comment in f88c31c in #2399. cc @mikejolley

@nerrad
Copy link
Contributor

nerrad commented May 20, 2020

The latest commit I pushed (202e5a2) fixes an issue where the context was not being seen by atomic components within the atomic blocks for the single product block. Essentially the problem was that the single product block was using an instance of InnerBlockConfigurationProvider that was different than the instance built in the all-products bundle (which is where the atomic blocks were registered). Thus the components being rendered were from the atomic blocks in the all-products bundle and their usage of useInnerBlockConfigurationContext was assuming the same instance of the context defined in the all-products bundle.

The fix, is to push the InnerBlockConfigurationProvider and it's hook into a separately built shared library. That way we're ensuring both blocks are using the correct instance.

Going forward, we should probably take some time to figure out how we want to approach this because there are implications either way.

Build a bundle for shared context.

Anytime we share context across bundles that implement the context wrapping atomic blocks, we should put that context in the new shared context build I did in the commit.

Pros:

  • there's a clear export for shared context.

Cons:

  • we really have to watch what we put in this shared context build because there's risk of circular dependencies if it needs things like hooks or components (basically, everything in this bundle must be self-contained).
  • This bundle could grow over time, and it would basically mean anything using this bundle will get all contexts loaded via the external (instead of just what is needed in the build). This mostly has impact on the frontend. We could get around that by just having frontend builds import directly from the context folder since the frontend doesn't register blocks.

Other options?

I thought, maybe we could have atomic blocks built to their own bundle. If we went this way, it still won't work because we'd run into the same problem we have now with the context defined in the atomic blocks bundle being different than that used by the context provider defined in the various parent block bundles.

The only way around this would be to do some fancy build alias swapping so editor builds pull the context from the atomic blocks external and frontend builds pull from the context from the main file.

What should we do?

Since any approach I can think of results in differing builds between frontend and backend, I think we should just go with the approach I implemented here for now but only implement usage of the external for editor builds. On the frontend we can alias @woocommerce/shared-context to direct imports.

Eventually, we're going to have to revisit this when/if we implement InnerBlocks in cart and checkout. That will be tricky because all the hooks used in the cart and checkout contexts create some circular dependencies when the contexts are built to a separate build.

@mikejolley mikejolley marked this pull request as ready for review May 21, 2020 14:22
@mikejolley mikejolley requested a review from a team as a code owner May 21, 2020 14:22
@mikejolley mikejolley requested review from Aljullu and removed request for a team May 21, 2020 14:22
Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

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

Wow, this looks really great @mikejolley, good job! I'm super excited about this block. 🎉

I didn't take a look at all the code yet, but will leave some early feedback about some things I noticed and that I couldn't find in the follow-up issues. If you were already accounting for them, feel free to ignore this comment. 🙂

  • Product Image: The Sale badge doesn't seem to work for me:
    Peek 2020-05-22 13-28
  • Product Title: the Link to Product Page attribute doesn't have any effect in the frontend.
  • I'm not allowed to add inner blocks specific to the product (in the screenshot, I was trying to add back the Product Summary block).
    imatge
  • The position of inner blocks is not persisted in the frontend:
Editor Frontend
imatge imatge

@mikejolley
Copy link
Member Author

I'm not allowed to add inner blocks specific to the product (in the screenshot, I was trying to add back the Product Summary block).

This is due to using columns blocks which have their own Inner Block configs. We'll need to look at this as part of #2526, either fixing columns or adding a custom column block.

@mikejolley
Copy link
Member Author

Product Image: The Sale badge doesn't seem to work for me:

There is a bug here, but also it could be when a product is not on sale. This seems to be something we'll need to work out of the design side since we're working with live data cc @LevinMedia

@@ -0,0 +1,150 @@
/**
* External dependencies
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved contents of Atomic Components to a block.js per atomic block. This is so we can reuse the same block for edit.js and frontend.js, and also allows frontend.js to see the defined blockAttributes so it can map dataset to block attributes correctly.

/**
* Internal dependencies
*/
export const blockAttributes = {
Copy link
Member Author

Choose a reason for hiding this comment

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

Defining these separately so the frontend.js can validate attributes against them.


const Save = ( { attributes } ) => {
return (
<div className={ classnames( 'is-loading', attributes.className ) } />
Copy link
Member Author

Choose a reason for hiding this comment

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

This is new; saves some markup for the InnerBlock so we can render from this on the frontend instead of using layoutconfig.

*
* @param {Array} template Inner Blocks Template.
*/
export const createBlocksFromTemplate = ( template ) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This utility will create blocks recursively from a defined default template. Used currently to reset the layout back to default.

/**
* Internal dependencies
*/
import ProductButton from '../blocks/product/button/frontend';
Copy link
Member Author

Choose a reason for hiding this comment

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

Rather than using atomic/components, this is mapping to the frontend.js file of each defined Atomic Block. The frontend.js file is responsible for converting dataset attributes to block attributes, then rendering the block's component.

*
* @param {Object[]} innerBlocks Inner block components.
*/
export const getLayoutConfig = ( innerBlocks ) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved from base-utils. This might be removable in a follow up when layout configs are deprecated/removed.

* @param {Object[]} layoutConfig Object with component data.
* @param {number} componentId Parent component ID needed for key generation.
*/
export const renderProductLayout = (
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved from product-list-item/utils.js

return (
<LayoutComponent
key={ keyParts.join( '_' ) }
attributes={ attributes }
Copy link
Member Author

Choose a reason for hiding this comment

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

This was changed to maintain compatibility with the atomic blocks - before we just passed all props.

@@ -0,0 +1,148 @@
/**
* External dependencies
Copy link
Member Author

Choose a reason for hiding this comment

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

These utils were moved form assets/js/utils since they are shared and used throughout.

* @param {string} props.keyPrefix Prefix for keys.
* @param {Object} props.blockMap Child blocks will be mapped to components.
*/
const renderChildren = ( { children, blockMap, keyPrefix = '' } ) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is where the new magic happens; this converts inner blocks into usable components.

mikejolley and others added 13 commits May 26, 2020 17:46
This fixes an issue where blocks that use contexts are using different instances
* renderChildren method with enhancement to renderFrontend

* Update renderFrontend usage

* Remove core components from block map

* Register atomic blocks with save method

* Atomic components to use context

* Break out single block save method

* Update singlge product

* Atomic PHP side registration

* Fix element attributes

* Undo atomic block reg changes

* Implement reset layout

* Tweak loading states
@mikejolley mikejolley force-pushed the add/single-product-block branch from 09cd9d2 to a2121de Compare May 26, 2020 16:48
@LevinMedia
Copy link
Contributor

There is a bug here, but also it could be when a product is not on sale. This seems to be something we'll need to work out of the design side since we're working with live data

@mikejolley I'll be looking into innerblock settings this week, and I'll be sure to give that one some thought.

@nerrad nerrad deleted the add/single-product-block branch August 4, 2021 13:35
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: new block Applied to work that introduces a new block (typically used on an epic issue).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Investigate Inner Block rendering without the use of LayoutConfig [Single Product Block] Block Creation
4 participants