Skip to content

Commit

Permalink
Add Product Query Support for Atomic Rating Block (woocommerce#7352)
Browse files Browse the repository at this point in the history
* Add PQ support for client-side.

Set up the block for PQ support and add necessary adjustments for the
editor. Will address dynamic save functionality in a following commit.

* Add dynamic render function for PQ support.

* Add dynamic render callback for SSR.

* Remove client-side Save function.

* Add PQ Context interface to shared type defs.

* Convert all block JS files to TS.

* Remove commented import from block file.

* Add typecasting to block function params.

As a workaround, added a general Record type but left a TODO to revisit
the proper object, as there is a mismatch in the shape of the default
object property types and the actual types.

* Update inserter behavior.

Allows for the ability to add the rating block from in the inserter
(as long as it's an inner block of the listed parents in the config).
Also disables the placeholder product selector from being rendered
unnecessarily (i.e., when the context ID is present).

* Update parent inner blocks config.

Reassign parent array to ancestor array which allows for blocks to be
included with more flexibility - i.e., added within groups that are
children of the ancestor block.

* Add productID to rating Attributes interface.

* TS type casting and import adustments.

Some adjustments to utilize types that we already have available, along
with some syntax adjustments and more sensible import tweaks.

* Update type-casting to use ProductResponseItem

Instead of using the generic Record, we can utilize the
ProductResponseItem interface and set an omission for the average_rating
property until that is corrected to properly reflect the API response.

* Add alias to blocks dir for imports.

Allows us to use exports from the blocks dir as "external" imports. This
way we do not need to write long, relative import paths (which can be
fragile in the long run).
  • Loading branch information
danielwrobert authored and senadir committed Nov 12, 2022
1 parent 128df71 commit 5495991
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ export const blockAttributes = {
type: 'number',
default: 0,
},
isDescendentOfQueryLoop: {
type: 'boolean',
default: false,
},
};

export default blockAttributes;
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { __, _n, sprintf } from '@wordpress/i18n';
import classnames from 'classnames';
import {
Expand All @@ -14,20 +13,43 @@ import {
useTypographyProps,
} from '@woocommerce/base-hooks';
import { withProductDataContext } from '@woocommerce/shared-hocs';
import { isNumber, ProductResponseItem } from '@woocommerce/types';

/**
* Internal dependencies
*/
import './style.scss';

type Props = {
className?: string;
};

const getAverageRating = (
product: Omit< ProductResponseItem, 'average_rating' > & {
average_rating: string;
}
) => {
const rating = parseFloat( product.average_rating );

return Number.isFinite( rating ) && rating > 0 ? rating : 0;
};

const getRatingCount = ( product: ProductResponseItem ) => {
const count = isNumber( product.review_count )
? product.review_count
: parseInt( product.review_count, 10 );

return Number.isFinite( count ) && count > 0 ? count : 0;
};

/**
* Product Rating Block Component.
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @return {*} The component.
*/
export const Block = ( props ) => {
export const Block = ( props: Props ): JSX.Element | null => {
const { parentClassName } = useInnerBlockLayoutContext();
const { product } = useProductDataContext();
const rating = getAverageRating( product );
Expand Down Expand Up @@ -96,20 +118,4 @@ export const Block = ( props ) => {
);
};

const getAverageRating = ( product ) => {
const rating = parseFloat( product.average_rating );

return Number.isFinite( rating ) && rating > 0 ? rating : 0;
};

const getRatingCount = ( product ) => {
const count = parseInt( product.review_count, 10 );

return Number.isFinite( count ) && count > 0 ? count : 0;
};

Block.propTypes = {
className: PropTypes.string,
};

export default withProductDataContext( Block );
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,40 @@
*/
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { useEffect } from 'react';
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';

/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
import { Attributes } from './types';

const Edit = ( { attributes } ) => {
const Edit = ( {
attributes,
setAttributes,
context,
}: BlockEditProps< Attributes > & { context: Context } ): JSX.Element => {
const blockProps = useBlockProps( {
className: 'wp-block-woocommerce-product-rating',
} );
const blockAttrs = {
...attributes,
...context,
};
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );

useEffect(
() => setAttributes( { isDescendentOfQueryLoop } ),
[ setAttributes, isDescendentOfQueryLoop ]
);

return (
<div { ...blockProps }>
<Block { ...attributes } />
<Block { ...blockAttrs } />
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import type { BlockConfiguration } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -15,17 +16,21 @@ import {
BLOCK_DESCRIPTION as description,
} from './constants';
import { supports } from './support';
import { Save } from './save';

const blockConfig = {
const blockConfig: BlockConfiguration = {
apiVersion: 2,
title,
description,
usesContext: [ 'query', 'queryId', 'postId' ],
ancestor: [
'@woocommerce/all-products',
'@woocommerce/single-product',
'core/post-template',
],
icon: { src: icon },
attributes,
supports,
edit,
save: Save,
};

registerBlockType( 'woocommerce/product-rating', {
Expand Down
4 changes: 4 additions & 0 deletions assets/js/atomic/blocks/product-elements/rating/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Attributes {
productId: number;
isDescendentOfQueryLoop: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ const withProductSelector = ( selectorArgs ) => ( OriginalComponent ) => {
const { productId } = attributes;
const [ isEditing, setIsEditing ] = useState( ! productId );

if ( productDataContext.hasContext ) {
if (
productDataContext.hasContext ||
Number.isFinite( props.context?.queryId )
) {
return <OriginalComponent { ...props } />;
}

Expand Down
5 changes: 5 additions & 0 deletions assets/js/blocks/product-query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export interface QueryBlockQuery {
taxQuery?: string;
}

export interface ProductQueryContext {
query?: QueryBlockQuery & ProductQueryArguments;
queryId?: number;
}

export enum QueryVariation {
/** The main, fully customizable, Product Query block */
PRODUCT_QUERY = 'woocommerce/product-query',
Expand Down
4 changes: 4 additions & 0 deletions bin/webpack-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const getAlias = ( options = {} ) => {
__dirname,
`../assets/js/${ pathPart }base/utils/`
),
'@woocommerce/blocks': path.resolve(
__dirname,
`../assets/js/${ pathPart }/blocks`
),
'@woocommerce/editor-components': path.resolve(
__dirname,
`../assets/js/${ pathPart }editor-components/`
Expand Down
67 changes: 63 additions & 4 deletions src/BlockTypes/ProductRating.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,71 @@ protected function get_block_type_supports() {
}

/**
* Register script and style assets for the block type before it is registered.
* Overwrite parent method to prevent script registration.
*
* This registers the scripts; it does not enqueue them.
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return null;
}

/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}

/**
* Filter the output from wc_get_rating_html.
*
* @param string $html Star rating markup. Default empty string.
* @param float $rating Rating being shown.
* @param int $count Total number of ratings.
* @return string
*/
public function filter_rating_html( $html, $rating, $count ) {
if ( 0 < $rating ) {
/* translators: %s: rating */
$label = sprintf( __( 'Rated %s out of 5', 'woo-gutenberg-products-block' ), $rating );
$html = '<div class="wc-block-components-product-rating__stars wc-block-grid__product-rating__stars" role="img" aria-label="' . esc_attr( $label ) . '">' . wc_get_star_rating_html( $rating, $count ) . '</div>';
}
return $html;
}

/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}

$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );

add_filter(
'woocommerce_product_get_rating_html',
[ $this, 'filter_rating_html' ],
10,
3
);

if ( $product ) {
return sprintf(
'<div class="wc-block-components-product-rating wc-block-grid__product-rating">
%s
</div>',
wc_get_rating_html( $product->get_average_rating() )
);
}
}
}
1 change: 1 addition & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@woocommerce/base-hocs/*": [ "assets/js/base/hocs/*" ],
"@woocommerce/base-hooks": [ "assets/js/base/hooks" ],
"@woocommerce/base-utils": [ "assets/js/base/utils" ],
"@woocommerce/blocks/*": [ "assets/js/blocks/*" ],
"@woocommerce/editor-components/*": [
"assets/js/editor-components/*"
],
Expand Down

0 comments on commit 5495991

Please sign in to comment.