diff --git a/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js b/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js
index d2ad073a2da..2f75838b081 100644
--- a/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js
+++ b/plugins/woocommerce-admin/client/analytics/report/products/table-variations.js
@@ -2,7 +2,7 @@
/**
* External dependencies
*/
-import { __, _n } from '@wordpress/i18n';
+import { __, _n, _x } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { map, get } from 'lodash';
@@ -18,6 +18,7 @@ import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
*/
import ReportTable from 'analytics/components/report-table';
import { numberFormat } from 'lib/number';
+import { isLowStock } from './utils';
export default class VariationsReportTable extends Component {
constructor() {
@@ -27,13 +28,6 @@ export default class VariationsReportTable extends Component {
this.getRowsContent = this.getRowsContent.bind( this );
}
- getVariationName( row ) {
- const extendedInfo = get( row, 'extended_info', {} );
- const attributes = get( extendedInfo, 'attributes', {} );
-
- return extendedInfo.name + ' / ' + attributes.map( a => a.option ).join( ', ' );
- }
-
getHeadersContent() {
return [
{
@@ -82,15 +76,9 @@ export default class VariationsReportTable extends Component {
const persistedQuery = getPersistedQuery( query );
return map( data, row => {
- const {
- items_sold,
- gross_revenue,
- orders_count,
- stock_status = 'outofstock',
- stock_quantity = '0',
- product_id,
- } = row;
- const name = this.getVariationName( row );
+ const { items_sold, gross_revenue, orders_count, extended_info, product_id } = row;
+ const { stock_status, stock_quantity, low_stock_amount } = extended_info;
+ const name = get( row, [ 'extended_info', 'name' ], '' ).replace( ' - ', ' / ' );
const ordersLink = getNewPath( persistedQuery, 'orders', {
filter: 'advanced',
product_includes: query.products,
@@ -123,10 +111,12 @@ export default class VariationsReportTable extends Component {
value: orders_count,
},
{
- display: (
+ display: isLowStock( stock_status, stock_quantity, low_stock_amount ) ? (
- { stockStatuses[ stock_status ] }
+ { _x( 'Low', 'Indication of a low quantity', 'wc-admin' ) }
+ ) : (
+ stockStatuses[ stock_status ]
),
value: stockStatuses[ stock_status ],
},
diff --git a/plugins/woocommerce-admin/client/analytics/report/products/table.js b/plugins/woocommerce-admin/client/analytics/report/products/table.js
index 9f2f7fffb4c..7ccbc2d84b0 100644
--- a/plugins/woocommerce-admin/client/analytics/report/products/table.js
+++ b/plugins/woocommerce-admin/client/analytics/report/products/table.js
@@ -2,7 +2,7 @@
/**
* External dependencies
*/
-import { __, _n } from '@wordpress/i18n';
+import { __, _n, _x } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { map } from 'lodash';
@@ -18,6 +18,7 @@ import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
*/
import ReportTable from 'analytics/components/report-table';
import { numberFormat } from 'lib/number';
+import { isLowStock } from './utils';
export default class ProductsReportTable extends Component {
constructor() {
@@ -97,10 +98,8 @@ export default class ProductsReportTable extends Component {
orders_count,
categories = [], // @TODO
variations = [], // @TODO
- stock_status = 'outofstock', // @TODO
- stock_quantity = '0', // @TODO
} = row;
- const { name } = extended_info;
+ const { name, stock_status, stock_quantity, low_stock_amount } = extended_info;
const ordersLink = getNewPath( persistedQuery, 'orders', {
filter: 'advanced',
product_includes: product_id,
@@ -150,10 +149,12 @@ export default class ProductsReportTable extends Component {
value: variations.length,
},
{
- display: (
+ display: isLowStock( stock_status, stock_quantity, low_stock_amount ) ? (
- { stockStatuses[ stock_status ] }
+ { _x( 'Low', 'Indication of a low quantity', 'wc-admin' ) }
+ ) : (
+ stockStatuses[ stock_status ]
),
value: stockStatuses[ stock_status ],
},
diff --git a/plugins/woocommerce-admin/client/analytics/report/products/utils.js b/plugins/woocommerce-admin/client/analytics/report/products/utils.js
new file mode 100644
index 00000000000..e966b61b240
--- /dev/null
+++ b/plugins/woocommerce-admin/client/analytics/report/products/utils.js
@@ -0,0 +1,15 @@
+/**
+ * Determine if a product or variation is in low stock.
+ *
+ * @format
+ * @param {number} threshold - The number at which stock is determined to be low.
+ * @returns {boolean} - Whether or not the stock is low.
+ */
+
+export function isLowStock( status, quantity, threshold ) {
+ if ( ! quantity ) {
+ // Sites that don't do inventory tracking will always return false.
+ return false;
+ }
+ return 'instock' === status && quantity <= threshold;
+}
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php
index e51d663b24c..ff348c10c78 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php
@@ -175,6 +175,56 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
'context' => array( 'view', 'edit' ),
'description' => __( 'Number of orders product appeared in.', 'wc-admin' ),
),
+ 'extended_info' => array(
+ 'name' => array(
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product name.', 'wc-admin' ),
+ ),
+ 'price' => array(
+ 'type' => 'number',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product price.', 'wc-admin' ),
+ ),
+ 'image' => array(
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product image.', 'wc-admin' ),
+ ),
+ 'permalink' => array(
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product link.', 'wc-admin' ),
+ ),
+ 'attributes' => array(
+ 'type' => 'array',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product attributes.', 'wc-admin' ),
+ ),
+ 'stock_status' => array(
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product inventory status.', 'wc-admin' ),
+ ),
+ 'stock_quantity' => array(
+ 'type' => 'integer',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product inventory quantity.', 'wc-admin' ),
+ ),
+ 'low_stock_amount' => array(
+ 'type' => 'integer',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product inventory threshold for low stock.', 'wc-admin' ),
+ ),
+ ),
),
);
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-variations-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-variations-controller.php
index 2ecd099b2c3..4091a3d604d 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-variations-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-variations-controller.php
@@ -31,6 +31,15 @@ class WC_Admin_REST_Reports_Variations_Controller extends WC_REST_Reports_Contro
*/
protected $rest_base = 'reports/variations';
+ /**
+ * Mapping between external parameter name and name used in query class.
+ *
+ * @var array
+ */
+ protected $param_mapping = array(
+ 'products' => 'product_includes',
+ );
+
/**
* Get items.
*
@@ -43,7 +52,11 @@ class WC_Admin_REST_Reports_Variations_Controller extends WC_REST_Reports_Contro
$registered = array_keys( $this->get_collection_params() );
foreach ( $registered as $param_name ) {
if ( isset( $request[ $param_name ] ) ) {
- $args[ $param_name ] = $request[ $param_name ];
+ if ( isset( $this->param_mapping[ $param_name ] ) ) {
+ $args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
+ } else {
+ $args[ $param_name ] = $request[ $param_name ];
+ }
}
}
@@ -202,6 +215,24 @@ class WC_Admin_REST_Reports_Variations_Controller extends WC_REST_Reports_Contro
'context' => array( 'view', 'edit' ),
'description' => __( 'Product attributes.', 'wc-admin' ),
),
+ 'stock_status' => array(
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product inventory status.', 'wc-admin' ),
+ ),
+ 'stock_quantity' => array(
+ 'type' => 'integer',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product inventory quantity.', 'wc-admin' ),
+ ),
+ 'low_stock_amount' => array(
+ 'type' => 'integer',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ 'description' => __( 'Product inventory threshold for low stock.', 'wc-admin' ),
+ ),
),
),
);
diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php
index e8bb1bd3c90..76c97dec01a 100644
--- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php
+++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php
@@ -60,6 +60,9 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
'price',
'image',
'permalink',
+ 'stock_status',
+ 'stock_quantity',
+ 'low_stock_amount',
);
/**
@@ -154,6 +157,10 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
$extended_info[ $extended_attribute ] = $value;
}
}
+ // If there is no set low_stock_amount, use the one in user settings.
+ if ( '' === $extended_info['low_stock_amount'] ) {
+ $extended_info['low_stock_amount'] = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
+ }
$extended_info = $this->cast_numbers( $extended_info );
}
$products_data[ $key ]['extended_info'] = $extended_info;
diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-variations-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-variations-data-store.php
index eae58846b6d..13d0b198648 100644
--- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-variations-data-store.php
+++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-variations-data-store.php
@@ -61,6 +61,9 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
'price',
'image',
'permalink',
+ 'stock_status',
+ 'stock_quantity',
+ 'low_stock_amount',
);
@@ -123,20 +126,23 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
foreach ( $products_data as $key => $product_data ) {
$extended_info = new ArrayObject();
if ( $query_args['extended_info'] ) {
+ $extended_attributes = apply_filters( 'woocommerce_rest_reports_variations_extended_attributes', $this->extended_attributes, $product_data );
$product = wc_get_product( $product_data['product_id'] );
- $extended_attributes = apply_filters( 'woocommerce_rest_reports_products_extended_attributes', $this->extended_attributes, $product_data );
- foreach ( $extended_attributes as $extended_attribute ) {
- $function = 'get_' . $extended_attribute;
- if ( is_callable( array( $product, $function ) ) && 'get_price' !== $function ) {
- $value = $product->{$function}();
- $extended_info[ $extended_attribute ] = $value;
- }
+ $variations = array();
+ if ( method_exists( $product, 'get_available_variations' ) ) {
+ $variations = $product->get_available_variations();
}
- $variations = $product->get_available_variations();
foreach ( $variations as $variation ) {
if ( (int) $variation['variation_id'] === (int) $product_data['variation_id'] ) {
$attributes = array();
$variation_product = wc_get_product( $variation['variation_id'] );
+ foreach ( $extended_attributes as $extended_attribute ) {
+ $function = 'get_' . $extended_attribute;
+ if ( is_callable( array( $variation_product, $function ) ) ) {
+ $value = $variation_product->{$function}();
+ $extended_info[ $extended_attribute ] = $value;
+ }
+ }
foreach ( $variation['attributes'] as $attribute_name => $attribute ) {
$name = str_replace( 'attribute_', '', $attribute_name );
$option_term = get_term_by( 'slug', $attribute, $name );
@@ -147,9 +153,12 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
);
}
$extended_info['attributes'] = $attributes;
- $extended_info['price'] = $variation_product->get_price();
}
}
+ // If there is no set low_stock_amount, use the one in user settings.
+ if ( '' === $extended_info['low_stock_amount'] ) {
+ $extended_info['low_stock_amount'] = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
+ }
$extended_info = $this->cast_numbers( $extended_info );
}
$products_data[ $key ]['extended_info'] = $extended_info;
diff --git a/plugins/woocommerce-admin/tests/api/reports-products.php b/plugins/woocommerce-admin/tests/api/reports-products.php
index a946650c22a..92d2f69e98f 100644
--- a/plugins/woocommerce-admin/tests/api/reports-products.php
+++ b/plugins/woocommerce-admin/tests/api/reports-products.php
@@ -5,6 +5,13 @@
* @package WooCommerce\Tests\API
* @since 3.5.0
*/
+
+/**
+ * Reports Products REST API Test Class
+ *
+ * @package WooCommerce\Tests\API
+ * @since 3.5.0
+ */
class WC_Tests_API_Reports_Products extends WC_REST_Unit_Test_Case {
/**
@@ -99,10 +106,11 @@ class WC_Tests_API_Reports_Products extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$properties = $data['schema']['properties'];
- $this->assertEquals( 4, count( $properties ) );
+ $this->assertEquals( 5, count( $properties ) );
$this->assertArrayHasKey( 'product_id', $properties );
$this->assertArrayHasKey( 'items_sold', $properties );
$this->assertArrayHasKey( 'gross_revenue', $properties );
$this->assertArrayHasKey( 'orders_count', $properties );
+ $this->assertArrayHasKey( 'extended_info', $properties );
}
}
diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php
index b82e8d06085..d213b444c4a 100644
--- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php
+++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php
@@ -1,11 +1,17 @@
assertEquals( $expected_data, $query->get_data() );
}
+ /**
+ * Test the ordering of results by product name
+ *
+ * @since 3.5.0
+ */
public function test_order_by_product_name() {
WC_Helper_Reports::reset_stats_dbs();
@@ -183,7 +194,11 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case {
$product = new WC_Product_Simple();
$product->set_name( 'Test Product' );
$product->set_regular_price( 25 );
+ $product->set_manage_stock( true );
+ $product->set_stock_quantity( 25 );
+ $product->set_low_stock_amount( 5 );
$product->save();
+
$order = WC_Helper_Order::create_order( 1, $product );
$order->set_status( 'completed' );
$order->set_shipping_total( 10 );
@@ -214,10 +229,13 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case {
'gross_revenue' => 100.0, // $25 * 4.
'orders_count' => 1,
'extended_info' => array(
- 'name' => $product->get_name(),
- 'image' => $product->get_image(),
- 'permalink' => $product->get_permalink(),
- 'price' => (float) $product->get_price(),
+ 'name' => $product->get_name(),
+ 'image' => $product->get_image(),
+ 'permalink' => $product->get_permalink(),
+ 'price' => (float) $product->get_price(),
+ 'stock_status' => $product->get_stock_status(),
+ 'stock_quantity' => $product->get_stock_quantity() - 4, // subtract the ones purchased.
+ 'low_stock_amount' => $product->get_low_stock_amount(),
),
),
),
diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php
index bba18f73fea..89075119860 100644
--- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php
+++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-variations.php
@@ -1,11 +1,17 @@
set_parent_id( $product->get_id() );
$variation->set_regular_price( 10 );
$variation->set_attributes( array( 'pa_color' => 'green' ) );
+ $variation->set_manage_stock( true );
+ $variation->set_stock_quantity( 25 );
$variation->save();
$order = WC_Helper_Order::create_order( 1, $variation );
@@ -120,11 +128,14 @@ class WC_Tests_Reports_Variations extends WC_Unit_Test_Case {
'gross_revenue' => 40.0, // $10 * 4.
'orders_count' => 1,
'extended_info' => array(
- 'name' => $product->get_name(),
- 'image' => $variation->get_image(),
- 'permalink' => $product->get_permalink(),
- 'price' => (float) $variation->get_price(),
- 'attributes' => array(
+ 'name' => $variation->get_name(),
+ 'image' => $variation->get_image(),
+ 'permalink' => $variation->get_permalink(),
+ 'price' => (float) $variation->get_price(),
+ 'stock_status' => $variation->get_stock_status(),
+ 'stock_quantity' => $variation->get_stock_quantity() - 4, // subtract the ones purchased.
+ 'low_stock_amount' => get_option( 'woocommerce_notify_low_stock_amount' ),
+ 'attributes' => array(
0 => array(
'id' => 0,
'name' => 'color',