feat: collectionStats query added (#785)

* feat: collectionStats query added

* fix: `collectionStats` query completed and tested.

* chore: Linter and PHPStan compliance met

* chore: ProductTaxonomy values fixed.

* chore: CollectionStatsQueryTest tweaked for CI
This commit is contained in:
Geoffrey K Taylor
2023-09-23 03:50:17 -04:00
committed by GitHub
parent 356d705e19
commit 6b0c798f91
14 changed files with 922 additions and 3 deletions
+6
View File
@@ -43,6 +43,8 @@ class Type_Registry {
Type\WPEnum\Orders_Orderby_Enum::register();
Type\WPEnum\Id_Type_Enums::register();
Type\WPEnum\Cart_Error_Type::register();
Type\WPEnum\Product_Attribute_Enum::register();
Type\WPEnum\Attribute_Operator_Enum::register();
/**
* InputObjects.
@@ -60,6 +62,9 @@ class Type_Registry {
Type\WPInputObject\Product_Taxonomy_Filter_Input::register();
Type\WPInputObject\Product_Taxonomy_Input::register();
Type\WPInputObject\Orderby_Inputs::register();
Type\WPInputObject\Collection_Stats_Query_Input::register();
Type\WPInputObject\Collection_Stats_Where_Args::register();
Type\WPInputObject\Product_Attribute_Filter_Input::register();
/**
* Interfaces.
@@ -104,6 +109,7 @@ class Type_Registry {
Type\WPObject\Cart_Error_Types::register();
Type\WPObject\Payment_Token_Types::register();
Type\WPObject\Country_State_Type::register();
Type\WPObject\Collection_Stats_Type::register();
/**
* Object fields.
@@ -236,6 +236,8 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'type/enum/class-tax-rate-connection-orderby-enum.php';
require $include_directory_path . 'type/enum/class-tax-status.php';
require $include_directory_path . 'type/enum/class-taxonomy-operator.php';
require $include_directory_path . 'type/enum/class-attribute-operator-enum.php';
require $include_directory_path . 'type/enum/class-product-attribute-enum.php';
// Include interface type class files.
require $include_directory_path . 'type/interface/class-attribute.php';
@@ -278,6 +280,7 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'type/object/class-variation-attribute-type.php';
require $include_directory_path . 'type/object/class-payment-token-types.php';
require $include_directory_path . 'type/object/class-country-state-type.php';
require $include_directory_path . 'type/object/class-collection-stats-type.php';
// Include input type class files.
require $include_directory_path . 'type/input/class-cart-item-input.php';
@@ -293,6 +296,9 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
require $include_directory_path . 'type/input/class-product-taxonomy-input.php';
require $include_directory_path . 'type/input/class-shipping-line-input.php';
require $include_directory_path . 'type/input/class-tax-rate-connection-orderby-input.php';
require $include_directory_path . 'type/input/class-collection-stats-query-input.php';
require $include_directory_path . 'type/input/class-collection-stats-where-args.php';
require $include_directory_path . 'type/input/class-product-attribute-filter-input.php';
// Include mutation type class files.
require $include_directory_path . 'mutation/class-cart-add-fee.php';
+4
View File
@@ -427,6 +427,10 @@ class Products {
'type' => 'Boolean',
'description' => __( 'Include variations in the result set.', 'wp-graphql-woocommerce' ),
],
'rating' => [
'type' => [ 'list_of' => 'Integer' ],
'description' => __( 'Limit result set to products with a specific average rating. Must be between 1 and 5', 'wp-graphql-woocommerce' ),
],
];
if ( wc_tax_enabled() ) {
@@ -466,6 +466,19 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
}//end switch
}//end if
if ( ! empty( $where_args['rating'] ) ) {
$rating = $where_args['rating'];
$rating_terms = [];
foreach ( $rating as $value ) {
$rating_terms[] = 'rated-' . $value;
}
$tax_query[] = [
'taxonomy' => 'product_visibility',
'field' => 'name',
'terms' => $rating_terms,
];
}
// Process "taxonomyFilter".
$tax_filter_query = [];
if ( ! empty( $where_args['taxonomyFilter'] ) ) {
@@ -563,6 +576,8 @@ class Product_Connection_Resolver extends AbstractConnectionResolver {
$query_args[ $on_sale_key ] = $on_sale_ids;
}
/**
* {@inheritDoc}
*/
@@ -0,0 +1,33 @@
<?php
/**
* WPEnum Type - AttributeOperatorEnum
*
* @package WPGraphQL\WooCommerce\Type\WPEnum
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPEnum;
/**
* Class Attribute_Operator_Enum
*/
class Attribute_Operator_Enum {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_enum_type(
'AttributeOperatorEnum',
[
'description' => __( 'Collection statistic attributes operators', 'wp-graphql-woocommerce' ),
'values' => [
'IN' => [ 'value' => 'IN' ],
'NOT_IN' => [ 'value' => 'NOT IN' ],
'AND' => [ 'value' => 'AND' ],
],
]
);
}
}
@@ -0,0 +1,43 @@
<?php
/**
* WPEnum Type - ProductAttributeEnum
*
* @package WPGraphQL\WooCommerce\Type\WPEnum
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPEnum;
use WPGraphQL\Type\WPEnumType;
/**
* Class Product_Attribute_Enum
*/
class Product_Attribute_Enum {
/**
* Registers type
*
* @return void
*/
public static function register() {
// Get values from product attributes.
$taxonomy_values = [];
$taxonomies = wc_get_attribute_taxonomy_names();
foreach ( $taxonomies as $taxonomy ) {
$tax_object = get_taxonomy( $taxonomy );
if ( false !== $tax_object && in_array( 'product', $tax_object->object_type, true ) ) {
$taxonomy_values[ WPEnumType::get_safe_name( $taxonomy ) ] = [ 'value' => $taxonomy ];
}
}
register_graphql_enum_type(
'ProductAttributeEnum',
[
'description' => __( 'Product attribute taxonomies', 'wp-graphql-woocommerce' ),
'values' => $taxonomy_values,
]
);
}
}
@@ -28,7 +28,7 @@ class Product_Taxonomy {
$tax_object = get_taxonomy( $taxonomy );
if ( false !== $tax_object && in_array( 'product', $tax_object->object_type, true ) ) {
$taxonomy_values[ WPEnumType::get_safe_name( $tax_object->graphql_single_name ) ] = [ 'value' => $taxonomy ];
$taxonomy_values[ WPEnumType::get_safe_name( $taxonomy ) ] = [ 'value' => $taxonomy ];
}
}
@@ -0,0 +1,38 @@
<?php
/**
* WPInputObjectType - CollectionStatsQueryInput
*
* @package WPGraphQL\WooCommerce\Type\WPInputObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
/**
* Class Collection_Stats_Query_Input
*/
class Collection_Stats_Query_Input {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_input_type(
'CollectionStatsQueryInput',
[
'description' => __( 'Taxonomy query', 'wp-graphql-woocommerce' ),
'fields' => [
'taxonomy' => [
'type' => [ 'non_null' => 'ProductAttributeEnum' ],
'description' => __( 'Product Taxonomy', 'wp-graphql-woocommerce' ),
],
'relation' => [
'type' => [ 'non_null' => 'RelationEnum' ],
'description' => __( 'Taxonomy relation to query', 'wp-graphql-woocommerce' ),
],
],
]
);
}
}
@@ -0,0 +1,110 @@
<?php
/**
* WPInputObjectType - CollectionStatsWhereArgs
*
* @package WPGraphQL\WooCommerce\Type\WPInputObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
/**
* Class Collection_Stats_Where_Args
*/
class Collection_Stats_Where_Args {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_input_type(
'CollectionStatsWhereArgs',
[
'description' => __( 'Arguments used to filter the collection results', 'wp-graphql-woocommerce' ),
'fields' => [
'search' => [
'type' => 'String',
'description' => __( 'Limit result set to products based on a keyword search.', 'wp-graphql-woocommerce' ),
],
'slugIn' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'Limit result set to products with specific slugs.', 'wp-graphql-woocommerce' ),
],
'typeIn' => [
'type' => [ 'list_of' => 'ProductTypesEnum' ],
'description' => __( 'Limit result set to products assigned to a group of specific types.', 'wp-graphql-woocommerce' ),
],
'exclude' => [
'type' => [ 'list_of' => 'Int' ],
'description' => __( 'Ensure result set excludes specific IDs.', 'wp-graphql-woocommerce' ),
],
'include' => [
'type' => [ 'list_of' => 'Int' ],
'description' => __( 'Limit result set to specific ids.', 'wp-graphql-woocommerce' ),
],
'sku' => [
'type' => 'String',
'description' => __( 'Limit result set to products with specific SKU(s). Use commas to separate.', 'wp-graphql-woocommerce' ),
],
'featured' => [
'type' => 'Boolean',
'description' => __( 'Limit result set to featured products.', 'wp-graphql-woocommerce' ),
],
'parentIn' => [
'type' => [ 'list_of' => 'Int' ],
'description' => __( 'Specify objects whose parent is in an array.', 'wp-graphql-woocommerce' ),
],
'parentNotIn' => [
'type' => [ 'list_of' => 'Int' ],
'description' => __( 'Specify objects whose parent is not in an array.', 'wp-graphql-woocommerce' ),
],
'categoryIn' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'Limit result set to products assigned to a group of specific categories by name.', 'wp-graphql-woocommerce' ),
],
'categoryIdIn' => [
'type' => [ 'list_of' => 'Int' ],
'description' => __( 'Limit result set to products assigned to a specific group of category IDs.', 'wp-graphql-woocommerce' ),
],
'tagIn' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'Limit result set to products assigned to a specific group of tags by name.', 'wp-graphql-woocommerce' ),
],
'tagIdIn' => [
'type' => [ 'list_of' => 'Int' ],
'description' => __( 'Limit result set to products assigned to a specific group of tag IDs.', 'wp-graphql-woocommerce' ),
],
'attributes' => [
'type' => [ 'list_of' => 'ProductAttributeFilterInput' ],
'description' => __( 'Limit result set to products with a specific attribute. Use the taxonomy name/attribute slug.', 'wp-graphql-woocommerce' ),
],
'stockStatus' => [
'type' => [ 'list_of' => 'StockStatusEnum' ],
'description' => __( 'Limit result set to products in stock or out of stock.', 'wp-graphql-woocommerce' ),
],
'onSale' => [
'type' => 'Boolean',
'description' => __( 'Limit result set to products on sale.', 'wp-graphql-woocommerce' ),
],
'minPrice' => [
'type' => 'Float',
'description' => __( 'Limit result set to products based on a minimum price.', 'wp-graphql-woocommerce' ),
],
'maxPrice' => [
'type' => 'Float',
'description' => __( 'Limit result set to products based on a maximum price.', 'wp-graphql-woocommerce' ),
],
'visibility' => [
'type' => 'CatalogVisibilityEnum',
'description' => __( 'Limit result set to products with a specific visibility level.', 'wp-graphql-woocommerce' ),
],
'rating' => [
'type' => [ 'list_of' => 'Integer' ],
'description' => __( 'Limit result set to products with a specific average rating. Must be between 1 and 5', 'wp-graphql-woocommerce' ),
],
],
]
);
}
}
@@ -0,0 +1,46 @@
<?php
/**
* WPInputObjectType - ProductAttributeFilterInput
*
* @package WPGraphQL\WooCommerce\Type\WPInputObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPInputObject;
/**
* Class Product_Attribute_Filter_Input
*/
class Product_Attribute_Filter_Input {
/**
* Registers type
*
* @return void
*/
public static function register() {
register_graphql_input_type(
'ProductAttributeFilterInput',
[
'description' => __( 'Product filter', 'wp-graphql-woocommerce' ),
'fields' => [
'taxonomy' => [
'type' => [ 'non_null' => 'ProductAttributeEnum' ],
'description' => __( 'Which field to select taxonomy term by.', 'wp-graphql-woocommerce' ),
],
'terms' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'A list of term slugs', 'wp-graphql-woocommerce' ),
],
'ids' => [
'type' => [ 'list_of' => 'Int' ],
'description' => __( 'A list of term ids', 'wp-graphql-woocommerce' ),
],
'operator' => [
'type' => 'AttributeOperatorEnum',
'description' => __( 'Filter operation type', 'wp-graphql-woocommerce' ),
],
],
]
);
}
}
@@ -0,0 +1,380 @@
<?php
/**
* WPObject Type - Collection_Stats_Type
*
* @package WPGraphQL\WooCommerce\Type\WPObject
* @since TBD
*/
namespace WPGraphQL\WooCommerce\Type\WPObject;
/**
* Class Collection_Stats_Type
*/
class Collection_Stats_Type {
/**
* Register CollectionStats type to the WPGraphQL schema
*
* @return void
*/
public static function register() {
register_graphql_object_type(
'PriceRange',
[
'eagerlyLoadType' => true,
'description' => __( 'Price range', 'wp-graphql-woocommerce' ),
'fields' => [
'minPrice' => [
'type' => 'String',
'args' => [
'format' => [
'type' => 'PricingFieldFormatEnum',
'description' => __( 'Format of the price', 'wp-graphql-woocommerce' ),
],
],
'description' => __( 'Minimum price', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args ) {
if ( empty( $source['min_price'] ) ) {
return null;
}
if ( isset( $args['format'] ) && 'raw' === $args['format'] ) {
return $source['min_price'];
}
return wc_graphql_price( $source['min_price'] );
},
],
'maxPrice' => [
'type' => 'String',
'args' => [
'format' => [
'type' => 'PricingFieldFormatEnum',
'description' => __( 'Format of the price', 'wp-graphql-woocommerce' ),
],
],
'description' => __( 'Maximum price', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, array $args ) {
if ( empty( $source['max_price'] ) ) {
return null;
}
if ( isset( $args['format'] ) && 'raw' === $args['format'] ) {
return $source['max_price'];
}
return wc_graphql_price( $source['max_price'] );
},
],
],
]
);
register_graphql_object_type(
'AttributeCount',
[
'eagerlyLoadType' => true,
'description' => __( 'Product attribute terms count', 'wp-graphql-woocommerce' ),
'fields' => [
'slug' => [
'type' => [ 'non_null' => 'ProductAttributeEnum' ],
'description' => __( 'Attribute name', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
return $source->name;
},
],
'label' => [
'type' => [ 'non_null' => 'String' ],
'description' => __( 'Attribute taxonomy', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
$taxonomy = get_taxonomy( $source->name );
if ( ! $taxonomy instanceof \WP_Taxonomy ) {
return null;
}
return $taxonomy->label;
},
],
'name' => [
'type' => [ 'non_null' => 'String' ],
'description' => __( 'Attribute name', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
$taxonomy = get_taxonomy( $source->name );
if ( ! $taxonomy instanceof \WP_Taxonomy ) {
return null;
}
return $taxonomy->labels->singular_name;
},
],
'terms' => [
'type' => [ 'list_of' => 'SingleAttributeCount' ],
'description' => __( 'Attribute terms', 'wp-graphql-woocommerce' ),
],
],
]
);
register_graphql_object_type(
'SingleAttributeCount',
[
'eagerlyLoadType' => true,
'description' => __( 'Single attribute term count', 'wp-graphql-woocommerce' ),
'fields' => [
'termId' => [
'type' => [ 'non_null' => 'ID' ],
'description' => __( 'Term ID', 'wp-graphql-woocommerce' ),
],
'count' => [
'type' => 'Int',
'description' => __( 'Number of products.', 'wp-graphql-woocommerce' ),
],
'node' => [
'type' => 'TermNode',
'description' => __( 'Term object.', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
if ( empty( $source->termId ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
return null;
}
/**
* Term object.
*
* @var \WP_Term $term
*/
$term = get_term( $source->termId ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
if ( ! $term instanceof \WP_Term ) {
return null;
}
return new \WPGraphQL\Model\Term( $term );
},
],
],
]
);
register_graphql_object_type(
'RatingCount',
[
'eagerlyLoadType' => true,
'description' => __( 'Single rating count', 'wp-graphql-woocommerce' ),
'fields' => [
'rating' => [
'type' => [ 'non_null' => 'Int' ],
'description' => __( 'Average rating', 'wp-graphql-woocommerce' ),
],
'count' => [
'type' => 'Int',
'description' => __( 'Number of products', 'wp-graphql-woocommerce' ),
],
],
]
);
register_graphql_object_type(
'StockStatusCount',
[
'eagerlyLoadType' => true,
'description' => __( 'Single stock status count', 'wp-graphql-woocommerce' ),
'fields' => [
'status' => [
'type' => [ 'non_null' => 'StockStatusEnum' ],
'description' => __( 'Status', 'wp-graphql-woocommerce' ),
],
'count' => [
'type' => 'Int',
'description' => __( 'Number of products.', 'wp-graphql-woocommerce' ),
],
],
]
);
register_graphql_object_type(
'CollectionStats',
[
'description' => __( 'Data about a collection of products', 'wp-graphql-woocommerce' ),
'fields' => [
'priceRange' => [
'type' => 'PriceRange',
'description' => __( 'Min and max prices found in collection of products, provided using the smallest unit of the currency', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source ) {
$min_price = ! empty( $source['min_price'] ) ? $source['min_price'] : null;
$max_price = ! empty( $source['max_price'] ) ? $source['max_price'] : null;
return compact( 'min_price', 'max_price' );
},
],
'attributeCounts' => [
'type' => [ 'list_of' => 'AttributeCount' ],
'args' => [
'page' => [
'type' => 'Int',
'description' => __( 'Page of results to return', 'wp-graphql-woocommerce' ),
],
'perPage' => [
'type' => 'Int',
'description' => __( 'Number of results to return per page', 'wp-graphql-woocommerce' ),
],
],
'description' => __( 'Returns number of products within attribute terms', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, $args ) {
$page = ! empty( $args['page'] ) ? $args['page'] : 1;
$per_page = ! empty( $args['perPage'] ) ? $args['perPage'] : 0;
$attribute_counts = ! empty( $source['attribute_counts'] ) ? $source['attribute_counts'] : [];
$attribute_counts = array_slice(
$attribute_counts,
( $page - 1 ) * $per_page,
0 < $per_page ? $per_page : null
);
return array_map(
static function ( $name, $terms ) {
return (object) compact( 'name', 'terms' );
},
array_keys( $attribute_counts ),
array_values( $attribute_counts )
);
},
],
'ratingCounts' => [
'type' => [ 'list_of' => 'RatingCount' ],
'args' => [
'page' => [
'type' => 'Int',
'description' => __( 'Page of results to return', 'wp-graphql-woocommerce' ),
],
'perPage' => [
'type' => 'Int',
'description' => __( 'Number of results to return per page', 'wp-graphql-woocommerce' ),
],
],
'description' => __( 'Returns number of products with each average rating', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, $args ) {
$page = ! empty( $args['page'] ) ? $args['page'] : 1;
$per_page = ! empty( $args['perPage'] ) ? $args['perPage'] : 0;
$rating_counts = ! empty( $source['rating_counts'] ) ? $source['rating_counts'] : [];
$rating_counts = array_slice(
$rating_counts,
( $page - 1 ) * $per_page,
0 < $per_page ? $per_page : null
);
return $rating_counts;
},
],
'stockStatusCounts' => [
'type' => [ 'list_of' => 'StockStatusCount' ],
'args' => [
'page' => [
'type' => 'Int',
'description' => __( 'Page of results to return', 'wp-graphql-woocommerce' ),
],
'perPage' => [
'type' => 'Int',
'description' => __( 'Number of results to return per page', 'wp-graphql-woocommerce' ),
],
],
'description' => __( 'Returns number of products with each stock status', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $source, $args ) {
$page = ! empty( $args['page'] ) ? $args['page'] : 1;
$per_page = ! empty( $args['perPage'] ) ? $args['perPage'] : 0;
$stock_status_counts = ! empty( $source['stock_status_counts'] ) ? $source['stock_status_counts'] : [];
$stock_status_counts = array_slice(
$stock_status_counts,
( $page - 1 ) * $per_page,
0 < $per_page ? $per_page : null
);
return $stock_status_counts;
},
],
],
]
);
}
/**
* Prepare the WP_Rest_Request instance used for the resolution of a
* statistics for a product connection.
*
* @param array $where_args Arguments used to filter the connection results.
*
* @return \WP_REST_Request
*/
public static function prepare_rest_request( array $where_args = [] ) /* @phpstan-ignore-line */ {
$request = new \WP_REST_Request();
if ( empty( $where_args ) ) {
return $request;
}
$key_mapping = [
'slugIn' => 'slug',
'typeIn' => 'type',
'categoryIdIn' => 'category',
'tagIn' => 'tag',
'onSale' => 'on_sale',
'stockStatus' => 'stock_status',
'visibility' => 'catalog_visibility',
'minPrice' => 'min_price',
'maxPrice' => 'max_price',
];
$needs_formatting = [ 'attributes', 'categoryIn' ];
foreach ( $where_args as $key => $value ) {
if ( in_array( $key, $needs_formatting, true ) ) {
continue;
}
$request->set_param( $key_mapping[ $key ] ?? $key, $value );
}
if ( ! empty( $where_args['categoryIn'] ) ) {
$category_ids = array_map(
static function ( $category ) {
$term = get_term_by( 'slug', $category, 'product_cat' );
if ( is_object( $term ) ) {
return $term->term_id;
}
return 0;
},
$where_args['categoryIn']
);
$set_category = $request->get_param( 'category' );
if ( ! empty( $set_category ) ) {
$category_ids[] = $set_category;
$request->set_param( 'category', $category_ids );
} else {
$request->set_param( 'category', $category_ids );
}
$request->set_param( 'category_operator', 'and' );
}
if ( ! empty( $where_args['attributes'] ) ) {
$attributes = [];
foreach ( $where_args['attributes'] as $filter ) {
if ( str_starts_with( $filter['taxonomy'], 'pa_' ) ) {
$attribute = [];
$attribute['attribute'] = $filter['taxonomy'];
if ( ! empty( $filter['terms'] ) ) {
$attribute['slug'] = $filter['terms'];
} elseif ( ! empty( $filter['ids'] ) ) {
$attribute['term_id'] = $filter['ids'];
}
$attribute['operator'] = ! empty( $filter['operator'] ) ? strtolower( $filter['operator'] ) : 'in';
$attributes[] = $attribute;
} else {
if ( ! empty( $filter['ids'] ) ) {
continue;
}
$taxonomy = $filter['taxonomy'];
$request->set_param( "_unstable_tax_{$taxonomy}", $filter['ids'] );
$request->set_param( "_unstable_tax_{$taxonomy}_operator", strtolower( $filter['operator'] ) );
}
}
if ( ! empty( $attributes ) ) {
$request->set_param( 'attributes', $attributes );
}
}//end if
return $request;
}
}
+115
View File
@@ -8,6 +8,7 @@
namespace WPGraphQL\WooCommerce\Type\WPObject;
use Automattic\WooCommerce\StoreApi\Utilities\ProductQueryFilters;
use Automattic\WooCommerce\Utilities\OrderUtil;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ResolveInfo;
@@ -515,6 +516,120 @@ class Root_Query {
return [];
},
],
'collectionStats' => [
'type' => 'CollectionStats',
'args' => [
'calculatePriceRange' => [
'type' => 'Boolean',
'description' => __( 'If true, calculates the minimum and maximum product prices for the collection.', 'wp-graphql-woocommerce' ),
],
'calculateRatingCounts' => [
'type' => 'Boolean',
'description' => __( 'If true, calculates rating counts for products in the collection.', 'wp-graphql-woocommerce' ),
],
'calculateStockStatusCounts' => [
'type' => 'Boolean',
'description' => __( 'If true, calculates stock counts for products in the collection.', 'wp-graphql-woocommerce' ),
],
'taxonomies' => [
'type' => [ 'list_of' => 'CollectionStatsQueryInput' ],
],
'where' => [
'type' => 'CollectionStatsWhereArgs',
],
],
'description' => __( 'Statistics for a product taxonomy query', 'wp-graphql-woocommerce' ),
'resolve' => static function ( $_, $args ) {
$filters = new ProductQueryFilters(); // @phpstan-ignore-line
$data = [
'min_price' => null,
'max_price' => null,
'attribute_counts' => null,
'stock_status_counts' => null,
'rating_counts' => null,
];
// Process client-side filters.
$request = Collection_Stats_Type::prepare_rest_request( $args['where'] ?? [] );
// Format taxonomies.
if ( ! empty( $args['taxonomies'] ) ) {
$calculate_attribute_counts = [];
foreach ( $args['taxonomies'] as $attribute_to_count ) {
$calculate_attribute_counts[] = [
'taxonomy' => $attribute_to_count['taxonomy'],
'query_type' => strtolower( $attribute_to_count['relation'] ),
];
}
$request->set_param( 'calculate_attribute_counts', $calculate_attribute_counts );
}
$request->set_param( 'calculate_price_range', $args['calculatePriceRange'] ?? false );
$request->set_param( 'calculate_stock_status_counts', $args['calculateStockStatusCounts'] ?? false );
$request->set_param( 'calculate_rating_counts', $args['calculateRatingCounts'] ?? false );
if ( ! empty( $request['calculate_price_range'] ) ) {
$filter_request = clone $request;
$filter_request->set_param( 'min_price', null );
$filter_request->set_param( 'max_price', null );
$price_results = $filters->get_filtered_price( $filter_request ); // @phpstan-ignore-line
$data['min_price'] = $price_results->min_price;
$data['max_price'] = $price_results->max_price;
}
if ( ! empty( $request['calculate_stock_status_counts'] ) ) {
$filter_request = clone $request;
$counts = $filters->get_stock_status_counts( $filter_request ); // @phpstan-ignore-line
$data['stock_status_counts'] = [];
foreach ( $counts as $key => $value ) {
$data['stock_status_counts'][] = (object) [
'status' => $key,
'count' => $value,
];
}
}
if ( ! empty( $request['calculate_attribute_counts'] ) ) {
foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) {
if ( ! isset( $attributes_to_count['taxonomy'] ) ) {
continue;
}
$taxonomy = $attributes_to_count['taxonomy'];
$counts = $filters->get_attribute_counts( $request, $taxonomy ); // @phpstan-ignore-line
$data['attribute_counts'][ $taxonomy ] = [];
foreach ( $counts as $key => $value ) {
$data['attribute_counts'][ $taxonomy ][] = (object) [
'taxonomy' => $taxonomy,
'termId' => $key,
'count' => $value,
];
}
}
}
if ( ! empty( $request['calculate_rating_counts'] ) ) {
$filter_request = clone $request;
$counts = $filters->get_rating_counts( $filter_request ); // @phpstan-ignore-line
$data['rating_counts'] = [];
foreach ( $counts as $key => $value ) {
$data['rating_counts'][] = (object) [
'rating' => $key,
'count' => $value,
];
}
}
return $data;
},
],
]
);
+123
View File
@@ -0,0 +1,123 @@
<?php
class CollectionStatsQueryTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function setUp(): void {
parent::setUp();
update_option( 'woocommerce_attribute_lookup_enabled', 'yes' );
update_option( 'woocommerce_attribute_lookup_direct_updates', 'yes' );
}
public function testCollectionStatsQuery() {
$this->factory->product_variation->createSome(
$this->factory->product->createVariable()
);
$this->factory->product->createSimple();
$this->factory->product->createSimple();
$this->factory->product_variation->createSome(
$this->factory->product->createVariable()
);
$query = '
query ($where: CollectionStatsWhereArgs, $taxonomies: [CollectionStatsQueryInput]) {
collectionStats(
calculatePriceRange: true
calculateRatingCounts: true
calculateStockStatusCounts: true
taxonomies: $taxonomies
where: $where
) {
attributeCounts {
name
slug
label
terms {
node { slug }
termId
count
}
}
stockStatusCounts {
status
count
}
}
}
';
$variables = [
'where' => [
'attributes' => [
[
'taxonomy' => 'PA_COLOR',
'terms' => 'red',
'operator' => 'IN',
],
]
],
'taxonomies' => [
[
'taxonomy' => 'PA_COLOR',
'relation' => 'AND',
]
]
];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'collectionStats.attributeCounts',
[
$this->expectedField('slug', 'PA_COLOR' ),
$this->expectedField('label', 'color' ),
$this->expectedField('name', 'color' ),
$this->expectedNode(
'terms',
[
$this->expectedField( 'node.slug', 'red' ),
$this->expectedField( 'count', 2 ),
$this->expectedField( 'termId', self::NOT_FALSY ),
]
),
$this->expectedNode(
'terms',
[
$this->expectedField( 'node.slug', 'blue' ),
$this->expectedField( 'count', 2 ),
$this->expectedField( 'termId', self::NOT_FALSY ),
]
),
$this->expectedNode(
'terms',
[
$this->expectedField( 'node.slug', 'green' ),
$this->expectedField( 'count', 2 ),
$this->expectedField( 'termId', self::NOT_FALSY ),
]
),
],
0
),
$this->expectedNode(
'collectionStats.stockStatusCounts',
[
$this->expectedField( 'status', 'IN_STOCK' ),
$this->expectedField( 'count', 2 ),
]
),
$this->expectedNode(
'collectionStats.stockStatusCounts',
[
$this->expectedField( 'status', 'OUT_OF_STOCK' ),
$this->expectedField( 'count', 0 ),
]
),
$this->expectedNode(
'collectionStats.stockStatusCounts',
[
$this->expectedField( 'status', 'ON_BACKORDER' ),
$this->expectedField( 'count', 0 ),
]
),
];
$this->assertQuerySuccessful( $response, $expected );
}
}
+2 -2
View File
@@ -792,11 +792,11 @@ class ProductQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQ
'relation' => 'AND',
'filters' => [
[
'taxonomy' => 'PRODUCTCATEGORY',
'taxonomy' => 'PRODUCT_CAT',
'terms' => [ 'category-three' ],
],
[
'taxonomy' => 'PRODUCTCATEGORY',
'taxonomy' => 'PRODUCT_CAT',
'terms' => [ 'category-four' ],
'operator' => 'NOT_IN',
],