diff --git a/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js b/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js
index 54fc4581a25..11aeb5701c8 100644
--- a/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js
+++ b/plugins/woocommerce-admin/client/analytics/components/report-summary/index.js
@@ -19,7 +19,7 @@ import { SummaryList, SummaryListPlaceholder, SummaryNumber } from '@woocommerce
*/
import { getSummaryNumbers } from 'store/reports/utils';
import ReportError from 'analytics/components/report-error';
-import { calculateDelta, formatValue } from './utils';
+import { calculateDelta, formatValue } from 'lib/number';
import withSelect from 'wc-api/with-select';
/**
diff --git a/plugins/woocommerce-admin/client/analytics/components/report-summary/utils.js b/plugins/woocommerce-admin/client/analytics/components/report-summary/utils.js
deleted file mode 100644
index a4669da29c3..00000000000
--- a/plugins/woocommerce-admin/client/analytics/components/report-summary/utils.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/** @format */
-/**
- * External dependencies
- */
-import { isFinite } from 'lodash';
-
-/**
- * WooCommerce dependencies
- */
-import { formatCurrency } from '@woocommerce/currency';
-
-/**
- * Internal dependencies
- */
-import { numberFormat } from 'lib/number';
-
-export function formatValue( type, value ) {
- if ( ! isFinite( value ) ) {
- return null;
- }
-
- switch ( type ) {
- case 'average':
- return Math.round( value );
- case 'currency':
- return formatCurrency( value );
- case 'number':
- return numberFormat( value );
- }
-}
-
-export function calculateDelta( primaryValue, secondaryValue ) {
- if ( ! isFinite( primaryValue ) || ! isFinite( secondaryValue ) ) {
- return null;
- }
-
- if ( secondaryValue === 0 ) {
- return 0;
- }
-
- return Math.round( ( primaryValue - secondaryValue ) / secondaryValue * 100 );
-}
diff --git a/plugins/woocommerce-admin/client/dashboard/index.js b/plugins/woocommerce-admin/client/dashboard/index.js
index bd200f5efdc..91457ab5139 100644
--- a/plugins/woocommerce-admin/client/dashboard/index.js
+++ b/plugins/woocommerce-admin/client/dashboard/index.js
@@ -22,7 +22,7 @@ export default class Dashboard extends Component {
-
+
diff --git a/plugins/woocommerce-admin/client/dashboard/store-performance/index.js b/plugins/woocommerce-admin/client/dashboard/store-performance/index.js
index 849b226e740..a94ed659270 100644
--- a/plugins/woocommerce-admin/client/dashboard/store-performance/index.js
+++ b/plugins/woocommerce-admin/client/dashboard/store-performance/index.js
@@ -2,9 +2,19 @@
/**
* External dependencies
*/
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
import { ToggleControl } from '@wordpress/components';
import { Component, Fragment } from '@wordpress/element';
+import { compose } from '@wordpress/compose';
+import { withDispatch } from '@wordpress/data';
+import moment from 'moment';
+import { find } from 'lodash';
+
+/**
+ * WooCommerce dependencies
+ */
+import { getCurrentDates, appendTimestamp, getDateParamsFromQuery } from '@woocommerce/date';
+import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
/**
* Internal dependencies
@@ -16,99 +26,202 @@ import {
MenuTitle,
SectionHeader,
SummaryList,
+ SummaryListPlaceholder,
SummaryNumber,
} from '@woocommerce/components';
+import withSelect from 'wc-api/with-select';
import './style.scss';
+import { calculateDelta, formatValue } from 'lib/number';
class StorePerformance extends Component {
- constructor() {
- super( ...arguments );
+ constructor( props ) {
+ super( props );
this.state = {
- showCustomers: true,
- showProducts: true,
- showOrders: true,
+ userPrefs: props.userPrefs || [],
};
-
this.toggle = this.toggle.bind( this );
}
- toggle( type ) {
+ toggle( statKey ) {
return () => {
- this.setState( state => ( { [ type ]: ! state[ type ] } ) );
+ this.setState( state => {
+ const prefs = [ ...state.userPrefs ];
+ let newPrefs = [];
+ if ( ! prefs.includes( statKey ) ) {
+ prefs.push( statKey );
+ newPrefs = prefs;
+ } else {
+ newPrefs = prefs.filter( pref => pref !== statKey );
+ }
+ this.props.updateCurrentUserData( {
+ dashboard_performance_indicators: newPrefs,
+ } );
+ return {
+ userPrefs: newPrefs,
+ };
+ } );
};
}
renderMenu() {
+ const { indicators } = this.props;
return (
{ __( 'Display Stats:', 'wc-admin' ) }
-
-
-
+ { indicators.map( ( indicator, i ) => {
+ const checked = ! this.state.userPrefs.includes( indicator.stat );
+ return (
+
+ );
+ } ) }
);
}
- render() {
- const totalOrders = 10;
- const totalProducts = 1000;
- const { showCustomers, showProducts, showOrders } = this.state;
+ renderList() {
+ const {
+ query,
+ primaryRequesting,
+ secondaryRequesting,
+ primaryError,
+ secondaryError,
+ primaryData,
+ secondaryData,
+ userIndicators,
+ } = this.props;
+ if ( primaryRequesting || secondaryRequesting ) {
+ return ;
+ }
+ if ( primaryError || secondaryError ) {
+ return null;
+ }
+
+ const persistedQuery = getPersistedQuery( query );
+
+ const { compare } = getDateParamsFromQuery( query );
+ const prevLabel =
+ 'previous_period' === compare
+ ? __( 'Previous Period:', 'wc-admin' )
+ : __( 'Previous Year:', 'wc-admin' );
+ return (
+
+ { userIndicators.map( ( indicator, i ) => {
+ const primaryItem = find( primaryData.data, data => data.stat === indicator.stat );
+ const secondaryItem = find( secondaryData.data, data => data.stat === indicator.stat );
+
+ if ( ! primaryItem || ! secondaryItem ) {
+ return null;
+ }
+
+ const href =
+ ( primaryItem._links &&
+ primaryItem._links.report[ 0 ] &&
+ primaryItem._links.report[ 0 ].href ) ||
+ '';
+ const reportUrl =
+ ( href && getNewPath( persistedQuery, href, { chart: primaryItem.chart } ) ) || '';
+ const delta = calculateDelta( primaryItem.value, secondaryItem.value );
+ const primaryValue = formatValue( primaryItem.format, primaryItem.value );
+ const secondaryValue = formatValue( secondaryItem.format, secondaryItem.value );
+
+ return (
+
+ );
+ } ) }
+
+ );
+ }
+
+ render() {
return (
-
-
- { showCustomers && (
-
- ) }
- { showProducts && (
-
- ) }
- { showOrders && (
-
- ) }
-
-
+ { this.renderList() }
);
}
}
+export default compose(
+ withSelect( ( select, props ) => {
+ const { query } = props;
+ const {
+ getCurrentUserData,
+ getReportItems,
+ getReportItemsError,
+ isReportItemsRequesting,
+ } = select( 'wc-api' );
+ const userData = getCurrentUserData();
+ let userPrefs = userData.dashboard_performance_indicators;
-export default StorePerformance;
+ // Set default values for user preferences if none is set.
+ // These columns are HIDDEN by default.
+ if ( ! userPrefs ) {
+ userPrefs = [ 'taxes/order_tax', 'taxes/shipping_tax', 'downloads/download_count' ];
+ }
+
+ const datesFromQuery = getCurrentDates( query );
+ const endPrimary = datesFromQuery.primary.before;
+ const endSecondary = datesFromQuery.secondary.before;
+
+ const indicators = wcSettings.dataEndpoints.performanceIndicators;
+ const userIndicators = indicators.filter( indicator => ! userPrefs.includes( indicator.stat ) );
+ const statKeys = userIndicators.map( indicator => indicator.stat ).join( ',' );
+
+ const primaryQuery = {
+ after: appendTimestamp( datesFromQuery.primary.after, 'start' ),
+ before: appendTimestamp( endPrimary, endPrimary.isSame( moment(), 'day' ) ? 'now' : 'end' ),
+ stats: statKeys,
+ };
+
+ const secondaryQuery = {
+ after: appendTimestamp( datesFromQuery.secondary.after, 'start' ),
+ before: appendTimestamp(
+ endSecondary,
+ endSecondary.isSame( moment(), 'day' ) ? 'now' : 'end'
+ ),
+ stats: statKeys,
+ };
+
+ const primaryData = getReportItems( 'performance-indicators', primaryQuery );
+ const primaryError = getReportItemsError( 'performance-indicators', primaryQuery ) || null;
+ const primaryRequesting = isReportItemsRequesting( 'performance-indicators', primaryQuery );
+
+ const secondaryData = getReportItems( 'performance-indicators', secondaryQuery );
+ const secondaryError = getReportItemsError( 'performance-indicators', secondaryQuery ) || null;
+ const secondaryRequesting = isReportItemsRequesting( 'performance-indicators', secondaryQuery );
+
+ return {
+ userPrefs,
+ userIndicators,
+ indicators,
+ primaryData,
+ primaryError,
+ primaryRequesting,
+ secondaryData,
+ secondaryError,
+ secondaryRequesting,
+ };
+ } ),
+ withDispatch( dispatch => {
+ const { updateCurrentUserData } = dispatch( 'wc-api' );
+
+ return {
+ updateCurrentUserData,
+ };
+ } )
+)( StorePerformance );
diff --git a/plugins/woocommerce-admin/client/lib/number/index.js b/plugins/woocommerce-admin/client/lib/number/index.js
index 1cc17027cbe..c472eee0d5d 100644
--- a/plugins/woocommerce-admin/client/lib/number/index.js
+++ b/plugins/woocommerce-admin/client/lib/number/index.js
@@ -2,8 +2,9 @@
/**
* External dependencies
*/
-import { get } from 'lodash';
+import { get, isFinite } from 'lodash';
const number_format = require( 'locutus/php/strings/number_format' );
+import { formatCurrency } from '@woocommerce/currency';
/**
* Formats a number using site's current locale
@@ -13,7 +14,6 @@ const number_format = require( 'locutus/php/strings/number_format' );
* @param {int|null} [precision=null] optional decimal precision
* @returns {?String} A formatted string.
*/
-
export function numberFormat( number, precision = null ) {
if ( 'number' !== typeof number ) {
number = parseFloat( number );
@@ -34,3 +34,30 @@ export function numberFormat( number, precision = null ) {
return number_format( number, precision, decimalSeparator, thousandSeparator );
}
+
+export function formatValue( type, value ) {
+ if ( ! isFinite( value ) ) {
+ return null;
+ }
+
+ switch ( type ) {
+ case 'average':
+ return Math.round( value );
+ case 'currency':
+ return formatCurrency( value );
+ case 'number':
+ return numberFormat( value );
+ }
+}
+
+export function calculateDelta( primaryValue, secondaryValue ) {
+ if ( ! isFinite( primaryValue ) || ! isFinite( secondaryValue ) ) {
+ return null;
+ }
+
+ if ( secondaryValue === 0 ) {
+ return 0;
+ }
+
+ return Math.round( ( primaryValue - secondaryValue ) / secondaryValue * 100 );
+}
diff --git a/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js b/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js
index a850a119c60..ae7dd63f420 100644
--- a/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js
+++ b/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js
@@ -26,6 +26,7 @@ const typeEndpointMap = {
'report-items-query-downloads': 'downloads',
'report-items-query-customers': 'customers',
'report-items-query-stock': 'stock',
+ 'report-items-query-performance-indicators': 'performance-indicators',
};
function read( resourceNames, fetch = apiFetch ) {
diff --git a/plugins/woocommerce-admin/client/wc-api/user/operations.js b/plugins/woocommerce-admin/client/wc-api/user/operations.js
index 2c036b1fadd..0e4bb407ca0 100644
--- a/plugins/woocommerce-admin/client/wc-api/user/operations.js
+++ b/plugins/woocommerce-admin/client/wc-api/user/operations.js
@@ -40,6 +40,7 @@ function updateCurrentUserData( resourceNames, data, fetch ) {
'revenue_report_columns',
'taxes_report_columns',
'variations_report_columns',
+ 'dashboard_performance_indicators',
'dashboard_charts',
'dashboard_chart_type',
'dashboard_chart_interval',
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php
index bbf66396759..9e545e181db 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php
@@ -64,6 +64,7 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
return true;
}
+
/**
* Get all reports.
*
@@ -135,7 +136,36 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
),
);
+ /**
+ * Filter the list of allowed reports, so that data can be loaded from third party extensions in addition to WooCommerce core.
+ * Array items should be in format of array( 'slug' => 'downloads/stats', 'description' => '',
+ * 'url' => '', and 'path' => '/wc-ext/v1/...'.
+ *
+ * @param array $endpoints The list of allowed reports..
+ */
+ $reports = apply_filters( 'woocommerce_admin_reports', $reports );
+
foreach ( $reports as $report ) {
+ if ( empty( $report['slug'] ) ) {
+ continue;
+ }
+
+ if ( empty( $report['path'] ) ) {
+ $report['path'] = '/' . $this->namespace . '/reports/' . $report['slug'];
+ }
+
+ // Allows a different admin page to be loaded here,
+ // or allows an empty url if no report exists for a set of performance indicators.
+ if ( ! isset( $report['url'] ) ) {
+ if ( '/stats' === substr( $report['slug'], -6 ) ) {
+ $url_slug = substr( $report['slug'], 0, -6 );
+ } else {
+ $url_slug = $report['slug'];
+ }
+
+ $report['url'] = '/analytics/' . $url_slug;
+ }
+
$item = $this->prepare_item_for_response( (object) $report, $request );
$data[] = $this->prepare_response_for_collection( $item );
}
@@ -154,6 +184,7 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
$data = array(
'slug' => $report->slug,
'description' => $report->description,
+ 'path' => $report->path,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
@@ -165,7 +196,10 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
$response->add_links(
array(
'self' => array(
- 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $report->slug ) ),
+ 'href' => rest_url( $report->path ),
+ ),
+ 'report' => array(
+ 'href' => $report->url,
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
@@ -208,6 +242,12 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller {
'context' => array( 'view' ),
'readonly' => true,
),
+ 'path' => array(
+ 'description' => __( 'API path.', 'wc-admin' ),
+ 'type' => 'string',
+ 'context' => array( 'view' ),
+ 'readonly' => true,
+ ),
),
);
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php
index faf5cea16c9..187beb587da 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php
@@ -138,6 +138,8 @@ class WC_Admin_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Con
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'coupons_count' => array(
'description' => __( 'Amount of coupons.', 'wc-admin' ),
@@ -146,10 +148,11 @@ class WC_Admin_REST_Reports_Coupons_Stats_Controller extends WC_REST_Reports_Con
'readonly' => true,
),
'orders_count' => array(
- 'description' => __( 'Amount of orders.', 'wc-admin' ),
+ 'description' => __( 'Amount of discounted orders.', 'wc-admin' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
),
);
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php
index b7a5ec50ac6..1a622a6c9a5 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php
@@ -143,6 +143,7 @@ class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_C
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
),
);
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php
index e743ed90845..f62cf855aad 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php
@@ -146,18 +146,22 @@ class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_Admin_REST_Report
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
- ),
- 'avg_order_value' => array(
- 'description' => __( 'Average order value.', 'wc-admin' ),
- 'type' => 'number',
- 'context' => array( 'view', 'edit' ),
- 'readonly' => true,
+ 'format' => 'currency',
),
'orders_count' => array(
'description' => __( 'Amount of orders', 'wc-admin' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ ),
+ 'avg_order_value' => array(
+ 'description' => __( 'Average order value.', 'wc-admin' ),
+ 'type' => 'number',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'avg_items_per_order' => array(
'description' => __( 'Average items per order', 'wc-admin' ),
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php
index 3e7bde916f7..289aecc8067 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php
@@ -31,6 +31,67 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
*/
protected $rest_base = 'reports/performance-indicators';
+ /**
+ * Contains a list of endpoints by report slug.
+ *
+ * @var array
+ */
+ protected $endpoints = array();
+
+ /**
+ * Contains a list of allowed stats.
+ *
+ * @var array
+ */
+ protected $allowed_stats = array();
+
+ /**
+ * Contains a list of stat labels.
+ *
+ * @var array
+ */
+ protected $labels = array();
+
+ /**
+ * Contains a list of endpoints by url.
+ *
+ * @var array
+ */
+ protected $urls = array();
+
+ /**
+ * Register the routes for reports.
+ */
+ public function register_routes() {
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_items' ),
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
+ 'args' => $this->get_collection_params(),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/allowed',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_allowed_items' ),
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
+ 'args' => $this->get_collection_params(),
+ ),
+ 'schema' => array( $this, 'get_public_allowed_item_schema' ),
+ )
+ );
+ }
+
/**
* Maps query arguments from the REST request.
*
@@ -46,12 +107,15 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
}
/**
- * Get all allowed stats that can be returned from this endpoint.
+ * Get information such as allowed stats, stat labels, and endpoint data from stats reports.
*
- * @return array
+ * @return WP_Error|True
*/
- public function get_allowed_stats() {
- global $wp_rest_server;
+ private function get_indicator_data() {
+ // Data already retrieved.
+ if ( ! empty( $this->endpoints ) && ! empty( $this->labels ) && ! empty( $this->allowed_stats ) ) {
+ return true;
+ }
$request = new WP_REST_Request( 'GET', '/wc/v4/reports' );
$response = rest_do_request( $request );
@@ -63,9 +127,10 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
foreach ( $endpoints as $endpoint ) {
if ( '/stats' === substr( $endpoint['slug'], -6 ) ) {
- $request = new WP_REST_Request( 'OPTIONS', '/wc/v4/reports/' . $endpoint['slug'] );
+ $request = new WP_REST_Request( 'OPTIONS', $endpoint['path'] );
$response = rest_do_request( $request );
$data = $response->get_data();
+
$prefix = substr( $endpoint['slug'], 0, -6 );
if ( empty( $data['schema']['properties']['totals']['properties'] ) ) {
@@ -73,17 +138,113 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
}
foreach ( $data['schema']['properties']['totals']['properties'] as $property_key => $schema_info ) {
- $allowed_stats[] = $prefix . '/' . $property_key;
+ if ( empty( $schema_info['indicator'] ) || ! $schema_info['indicator'] ) {
+ continue;
+ }
+
+ $stat = $prefix . '/' . $property_key;
+ $allowed_stats[] = $stat;
+
+ $this->labels[ $stat ] = trim( preg_replace( '/\W+/', ' ', $schema_info['description'] ) );
+ $this->formats[ $stat ] = isset( $schema_info['format'] ) ? $schema_info['format'] : 'number';
}
+
+ $this->endpoints[ $prefix ] = $endpoint['path'];
+ $this->urls[ $prefix ] = $endpoint['_links']['report'][0]['href'];
}
}
+ $this->allowed_stats = $allowed_stats;
+ return true;
+ }
+
+ /**
+ * Returns a list of allowed performance indicators.
+ *
+ * @param WP_REST_Request $request Request data.
+ * @return array|WP_Error
+ */
+ public function get_allowed_items( $request ) {
+ $indicator_data = $this->get_indicator_data();
+ if ( is_wp_error( $indicator_data ) ) {
+ return $indicator_data;
+ }
+
+ $data = array();
+ foreach ( $this->allowed_stats as $stat ) {
+ $pieces = $this->get_stats_parts( $stat );
+ $report = $pieces[0];
+ $chart = $pieces[1];
+ $data[] = (object) array(
+ 'stat' => $stat,
+ 'chart' => $chart,
+ 'label' => $this->labels[ $stat ],
+ );
+ }
+
+ usort( $data, array( $this, 'sort' ) );
+
+ $objects = array();
+ foreach ( $data as $item ) {
+ $prepared = $this->prepare_item_for_response( $item, $request );
+ $objects[] = $this->prepare_response_for_collection( $prepared );
+ }
+
+ $response = rest_ensure_response( $objects );
+ $response->header( 'X-WP-Total', count( $data ) );
+ $response->header( 'X-WP-TotalPages', 1 );
+
+ $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
+
+ return $response;
+ }
+
+ /**
+ * Sorts the list of stats. Sorted by custom arrangement.
+ *
+ * @see https://github.com/woocommerce/wc-admin/issues/1282
+ * @param object $a First item.
+ * @param object $b Second item.
+ * @return order
+ */
+ public function sort( $a, $b ) {
/**
- * Filter the list of allowed stats that can be returned via the performance indiciator endpoint.
+ * Custom ordering for store performance indicators.
*
- * @param array $allowed_stats The list of allowed stats.
+ * @see https://github.com/woocommerce/wc-admin/issues/1282
+ * @param array $indicators A list of ordered indicators.
*/
- return apply_filters( 'woocommerce_admin_performance_indicators_allowed_stats', $allowed_stats );
+ $stat_order = apply_filters(
+ 'woocommerce_rest_report_sort_performance_indicators',
+ array(
+ 'revenue/gross_revenue',
+ 'revenue/net_revenue',
+ 'orders/orders_count',
+ 'orders/avg_order_value',
+ 'products/items_sold',
+ 'revenue/refunds',
+ 'coupons/orders_count',
+ 'coupons/amount',
+ 'taxes/total_tax',
+ 'taxes/order_tax',
+ 'taxes/shipping_tax',
+ 'revenue/shipping',
+ 'downloads/download_count',
+ )
+ );
+
+ $a = array_search( $a->stat, $stat_order );
+ $b = array_search( $b->stat, $stat_order );
+
+ if ( false === $a && false === $b ) {
+ return 0;
+ } elseif ( false === $a ) {
+ return 1;
+ } elseif ( false === $b ) {
+ return -1;
+ } else {
+ return $a - $b;
+ }
}
/**
@@ -93,10 +254,9 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
* @return array|WP_Error
*/
public function get_items( $request ) {
- $allowed_stats = $this->get_allowed_stats();
-
- if ( is_wp_error( $allowed_stats ) ) {
- return $allowed_stats;
+ $indicator_data = $this->get_indicator_data();
+ if ( is_wp_error( $indicator_data ) ) {
+ return $indicator_data;
}
$query_args = $this->prepare_reports_query( $request );
@@ -105,51 +265,50 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
}
$stats = array();
- foreach ( $query_args['stats'] as $stat_request ) {
+ foreach ( $query_args['stats'] as $stat ) {
$is_error = false;
- $pieces = explode( '/', $stat_request );
- $endpoint = $pieces[0];
- $stat = $pieces[1];
+ $pieces = $this->get_stats_parts( $stat );
+ $report = $pieces[0];
+ $chart = $pieces[1];
- if ( ! in_array( $stat_request, $allowed_stats ) ) {
+ if ( ! in_array( $stat, $this->allowed_stats ) ) {
continue;
}
- /**
- * Filter the list of allowed endpoints, so that data can be loaded from extensions rather than core.
- * These should be in the format of slug => path. Example: `bookings` => `/wc-bookings/v1/reports/bookings/stats`.
- *
- * @param array $endpoints The list of allowed endpoints.
- */
- $stats_endpoints = apply_filters( 'woocommerce_admin_performance_indicators_stats_endpoints', array() );
- if ( ! empty( $stats_endpoints [ $endpoint ] ) ) {
- $request_url = $stats_endpoints [ $endpoint ];
- } else {
- $request_url = '/wc/v4/reports/' . $endpoint . '/stats';
- }
-
- $request = new WP_REST_Request( 'GET', $request_url );
+ $request_url = $this->endpoints[ $report ];
+ $request = new WP_REST_Request( 'GET', $request_url );
$request->set_param( 'before', $query_args['before'] );
$request->set_param( 'after', $query_args['after'] );
$response = rest_do_request( $request );
- $data = $response->get_data();
- if ( 200 !== $response->get_status() || empty( $data['totals'][ $stat ] ) ) {
+ $data = $response->get_data();
+ $format = $this->formats[ $stat ];
+ $label = $this->labels[ $stat ];
+
+ if ( 200 !== $response->get_status() || ! isset( $data['totals'][ $chart ] ) ) {
$stats[] = (object) array(
- 'stat' => $stat_request,
- 'value' => null,
+ 'stat' => $stat,
+ 'chart' => $chart,
+ 'label' => $label,
+ 'format' => $format,
+ 'value' => null,
);
continue;
}
$stats[] = (object) array(
- 'stat' => $stat_request,
- 'value' => $data['totals'][ $stat ],
+ 'stat' => $stat,
+ 'chart' => $chart,
+ 'label' => $label,
+ 'format' => $format,
+ 'value' => $data['totals'][ $chart ],
);
}
+ usort( $stats, array( $this, 'sort' ) );
+
$objects = array();
foreach ( $stats as $stat ) {
$data = $this->prepare_item_for_response( $stat, $request );
@@ -201,25 +360,52 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
* @return array
*/
protected function prepare_links( $object ) {
- $pieces = explode( '/', $object->stat );
+ $pieces = $this->get_stats_parts( $object->stat );
$endpoint = $pieces[0];
$stat = $pieces[1];
+ $url = $this->urls[ $endpoint ];
$links = array(
+ 'api' => array(
+ 'href' => rest_url( $this->endpoints[ $endpoint ] ),
+ ),
'report' => array(
- 'href' => rest_url( sprintf( '/%s/reports/%s/stats', $this->namespace, $endpoint ) ),
+ 'href' => ! empty( $url ) ? $url : '',
),
);
return $links;
}
+ /**
+ * Returns the endpoint part of a stat request (prefix) and the actual stat total we want.
+ * To allow extensions to namespace (example: fue/emails/sent), we break on the last forward slash.
+ *
+ * @param string $full_stat A stat request string like orders/avg_order_value or fue/emails/sent.
+ * @return array Containing the prefix (endpoint) and suffix (stat).
+ */
+ private function get_stats_parts( $full_stat ) {
+ $endpoint = substr( $full_stat, 0, strrpos( $full_stat, '/' ) );
+ $stat = substr( $full_stat, ( strrpos( $full_stat, '/' ) + 1 ) );
+ return array(
+ $endpoint,
+ $stat,
+ );
+ }
+
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
+ $indicator_data = $this->get_indicator_data();
+ if ( is_wp_error( $indicator_data ) ) {
+ $allowed_stats = array();
+ } else {
+ $allowed_stats = $this->allowed_stats;
+ }
+
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report_performance_indicator',
@@ -230,6 +416,26 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'enum' => $allowed_stats,
+ ),
+ 'chart' => array(
+ 'description' => __( 'The specific chart this stat referrers to.', 'wc-admin' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'label' => array(
+ 'description' => __( 'Human readable label for the stat.', 'wc-admin' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'format' => array(
+ 'description' => __( 'Format of the stat.', 'wc-admin' ),
+ 'type' => 'number',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ 'enum' => array( 'number', 'currency' ),
),
'value' => array(
'description' => __( 'Value of the stat. Returns null if the stat does not exist or cannot be loaded.', 'wc-admin' ),
@@ -243,17 +449,29 @@ class WC_Admin_REST_Reports_Performance_Indicators_Controller extends WC_REST_Re
return $this->add_additional_fields_schema( $schema );
}
+ /**
+ * Get schema for the list of allowed performance indicators.
+ *
+ * @return array $schema
+ */
+ public function get_public_allowed_item_schema() {
+ $schema = $this->get_public_item_schema();
+ unset( $schema['properties']['value'] );
+ unset( $schema['properties']['format'] );
+ return $sceham;
+ }
+
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
- $allowed_stats = $this->get_allowed_stats();
- if ( is_wp_error( $allowed_stats ) ) {
+ $indicator_data = $this->get_indicator_data();
+ if ( is_wp_error( $indicator_data ) ) {
$allowed_stats = __( 'There was an issue loading the report endpoints', 'wc-admin' );
} else {
- $allowed_stats = implode( ', ', $allowed_stats );
+ $allowed_stats = implode( ', ', $this->allowed_stats );
}
$params = array();
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php
index 1db8c8a144e..087b90b599a 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-stats-controller.php
@@ -152,12 +152,14 @@ class WC_Admin_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Co
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
),
'net_revenue' => array(
'description' => __( 'Net revenue.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'format' => 'currency',
),
'orders_count' => array(
'description' => __( 'Number of orders.', 'wc-admin' ),
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php
index 697277c5260..324b780c4c5 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php
@@ -136,51 +136,61 @@ class WC_Admin_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Con
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'net_revenue' => array(
'description' => __( 'Net revenue.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'coupons' => array(
'description' => __( 'Total of coupons.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'format' => 'currency',
),
'shipping' => array(
'description' => __( 'Total of shipping.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'taxes' => array(
'description' => __( 'Total of taxes.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'format' => 'currency',
),
'refunds' => array(
'description' => __( 'Total of refunds.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'orders_count' => array(
- 'description' => __( 'Amount of orders', 'wc-admin' ),
+ 'description' => __( 'Amount of orders.', 'wc-admin' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'num_items_sold' => array(
- 'description' => __( 'Amount of orders', 'wc-admin' ),
+ 'description' => __( 'Items sold.', 'wc-admin' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'products' => array(
- 'description' => __( 'Amount of orders', 'wc-admin' ),
+ 'description' => __( 'Products sold.', 'wc-admin' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php
index 62756fcd64a..d545e468492 100644
--- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php
+++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php
@@ -167,18 +167,24 @@ class WC_Admin_REST_Reports_Taxes_Stats_Controller extends WC_REST_Reports_Contr
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'order_tax' => array(
'description' => __( 'Order tax.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'shipping_tax' => array(
'description' => __( 'Shipping tax.', 'wc-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
+ 'indicator' => true,
+ 'format' => 'currency',
),
'orders_count' => array(
'description' => __( 'Amount of orders.', 'wc-admin' ),
diff --git a/plugins/woocommerce-admin/lib/admin.php b/plugins/woocommerce-admin/lib/admin.php
index 78b3995e82a..bfc132c9295 100644
--- a/plugins/woocommerce-admin/lib/admin.php
+++ b/plugins/woocommerce-admin/lib/admin.php
@@ -399,6 +399,7 @@ function wc_admin_get_user_data_fields() {
'revenue_report_columns',
'taxes_report_columns',
'variations_report_columns',
+ 'dashboard_performance_indicators',
'dashboard_charts',
'dashboard_chart_type',
'dashboard_chart_interval',
diff --git a/plugins/woocommerce-admin/lib/client-assets.php b/plugins/woocommerce-admin/lib/client-assets.php
index 1c117a7560f..ae44494948d 100644
--- a/plugins/woocommerce-admin/lib/client-assets.php
+++ b/plugins/woocommerce-admin/lib/client-assets.php
@@ -153,7 +153,8 @@ function wc_admin_print_script_settings() {
}
$preload_data_endpoints = array(
- 'countries' => '/wc/v4/data/countries',
+ 'countries' => '/wc/v4/data/countries',
+ 'performanceIndicators' => '/wc/v4/reports/performance-indicators/allowed',
);
if ( function_exists( 'gutenberg_preload_api_request' ) ) {
@@ -169,7 +170,7 @@ function wc_admin_print_script_settings() {
$current_user_data = array();
foreach ( wc_admin_get_user_data_fields() as $user_field ) {
- $current_user_data[ $user_field ] = get_user_meta( get_current_user_id(), 'wc_admin_' . $user_field, true );
+ $current_user_data[ $user_field ] = json_decode( get_user_meta( get_current_user_id(), 'wc_admin_' . $user_field, true ) );
}
/**
@@ -179,22 +180,22 @@ function wc_admin_print_script_settings() {
// Settings and variables can be passed here for access in the app.
$settings = array(
- 'adminUrl' => admin_url(),
- 'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
- 'wcAdminAssetUrl' => plugins_url( 'images/', wc_admin_dir_path( 'wc-admin.php' ) ), // Temporary for plugin. See above.
- 'embedBreadcrumbs' => wc_admin_get_embed_breadcrumbs(),
- 'siteLocale' => esc_attr( get_bloginfo( 'language' ) ),
- 'currency' => wc_admin_currency_settings(),
- 'orderStatuses' => format_order_statuses( wc_get_order_statuses() ),
- 'stockStatuses' => wc_get_product_stock_status_options(),
- 'siteTitle' => get_bloginfo( 'name' ),
- 'trackingEnabled' => $tracking_enabled,
- 'dataEndpoints' => array(),
- 'l10n' => array(
+ 'adminUrl' => admin_url(),
+ 'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
+ 'wcAdminAssetUrl' => plugins_url( 'images/', wc_admin_dir_path( 'wc-admin.php' ) ), // Temporary for plugin. See above.
+ 'embedBreadcrumbs' => wc_admin_get_embed_breadcrumbs(),
+ 'siteLocale' => esc_attr( get_bloginfo( 'language' ) ),
+ 'currency' => wc_admin_currency_settings(),
+ 'orderStatuses' => format_order_statuses( wc_get_order_statuses() ),
+ 'stockStatuses' => wc_get_product_stock_status_options(),
+ 'siteTitle' => get_bloginfo( 'name' ),
+ 'trackingEnabled' => $tracking_enabled,
+ 'dataEndpoints' => array(),
+ 'l10n' => array(
'userLocale' => get_user_locale(),
'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
),
- 'currentUserData' => $current_user_data,
+ 'currentUserData' => $current_user_data,
);
foreach ( $preload_data_endpoints as $key => $endpoint ) {
diff --git a/plugins/woocommerce-admin/packages/components/CHANGELOG.md b/plugins/woocommerce-admin/packages/components/CHANGELOG.md
index 69071b1cca1..71066dfcf12 100644
--- a/plugins/woocommerce-admin/packages/components/CHANGELOG.md
+++ b/plugins/woocommerce-admin/packages/components/CHANGELOG.md
@@ -1,5 +1,6 @@
# 1.4.1 (unreleased)
- Chart component: format numbers and prices using store currency settings.
+- Make `href`/linking optional in SummaryNumber.
# 1.4.0
- Add download log ip address autocompleter to search component
diff --git a/plugins/woocommerce-admin/packages/components/src/summary/number.js b/plugins/woocommerce-admin/packages/components/src/summary/number.js
index 42dfa3020f1..dd4559ed454 100644
--- a/plugins/woocommerce-admin/packages/components/src/summary/number.js
+++ b/plugins/woocommerce-admin/packages/components/src/summary/number.js
@@ -52,17 +52,23 @@ const SummaryNumber = ( {
screenReaderLabel = sprintf( __( 'No change from %s', 'wc-admin' ), prevLabel );
}
- const Container = onToggle ? Button : Link;
+ let Container;
const containerProps = {
className: classes,
'aria-current': selected ? 'page' : null,
};
- if ( ! onToggle ) {
- containerProps.href = href;
- containerProps.role = 'menuitem';
+
+ if ( onToggle || href ) {
+ Container = onToggle ? Button : Link;
+ if ( ! onToggle ) {
+ containerProps.href = href;
+ containerProps.role = 'menuitem';
+ } else {
+ containerProps.onClick = onToggle;
+ containerProps[ 'aria-expanded' ] = isOpen;
+ }
} else {
- containerProps.onClick = onToggle;
- containerProps[ 'aria-expanded' ] = isOpen;
+ Container = 'div';
}
return (
@@ -109,7 +115,7 @@ SummaryNumber.propTypes = {
/**
* An internal link to the report focused on this number.
*/
- href: PropTypes.string.isRequired,
+ href: PropTypes.string,
/**
* Boolean describing whether the menu list is open. Only applies in mobile view,
* and only applies to the toggle-able item (first in the list).
@@ -147,7 +153,7 @@ SummaryNumber.propTypes = {
};
SummaryNumber.defaultProps = {
- href: '/analytics',
+ href: '',
isOpen: false,
prevLabel: __( 'Previous Period:', 'wc-admin' ),
reverseTrend: false,
diff --git a/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php b/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php
index ed4620924a8..e07457fd3c9 100644
--- a/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php
+++ b/plugins/woocommerce-admin/tests/api/reports-performance-indicators.php
@@ -37,6 +37,7 @@ class WC_Tests_API_Reports_Performance_Indicators extends WC_REST_Unit_Test_Case
$routes = $this->server->get_routes();
$this->assertArrayHasKey( $this->endpoint, $routes );
+ $this->assertArrayHasKey( $this->endpoint . '/allowed', $routes );
}
/**
@@ -96,14 +97,19 @@ class WC_Tests_API_Reports_Performance_Indicators extends WC_REST_Unit_Test_Case
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
-
$this->assertEquals( 2, count( $reports ) );
$this->assertEquals( 'orders/orders_count', $reports[0]['stat'] );
+ $this->assertEquals( 'Amount of orders', $reports[0]['label'] );
$this->assertEquals( 1, $reports[0]['value'] );
+ $this->assertEquals( 'orders_count', $reports[0]['chart'] );
+ $this->assertEquals( '/analytics/orders', $response->data[0]['_links']['report'][0]['href'] );
$this->assertEquals( 'downloads/download_count', $reports[1]['stat'] );
+ $this->assertEquals( 'Number of downloads', $reports[1]['label'] );
$this->assertEquals( 2, $reports[1]['value'] );
+ $this->assertEquals( 'download_count', $reports[1]['chart'] );
+ $this->assertEquals( '/analytics/downloads', $response->data[1]['_links']['report'][0]['href'] );
}
/**
@@ -148,8 +154,11 @@ class WC_Tests_API_Reports_Performance_Indicators extends WC_REST_Unit_Test_Case
$data = $response->get_data();
$properties = $data['schema']['properties'];
- $this->assertEquals( 2, count( $properties ) );
+ $this->assertEquals( 5, count( $properties ) );
$this->assertArrayHasKey( 'stat', $properties );
+ $this->assertArrayHasKey( 'chart', $properties );
+ $this->assertArrayHasKey( 'label', $properties );
+ $this->assertArrayHasKey( 'format', $properties );
$this->assertArrayHasKey( 'value', $properties );
}
}