Handle orphaned order statuses in analytics settings. (https://github.com/woocommerce/woocommerce-admin/pull/4090)

* Add method to retrieve all synced order statuses.

* Determine what unregistered order statuses we have and add to client-side settings object.

* Stop filtering out unregistered statuses from analytics settings.

* Add unregistered statuses to analytics settings fields.
This commit is contained in:
Jeff Stieler 2020-04-10 08:42:03 -06:00 committed by GitHub
parent 1e1daaa6bb
commit 9973e7e36f
3 changed files with 89 additions and 53 deletions

View File

@ -4,7 +4,7 @@
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks'; import { applyFilters } from '@wordpress/hooks';
import interpolateComponents from 'interpolate-components'; import interpolateComponents from 'interpolate-components';
import { ORDER_STATUSES } from '@woocommerce/wc-admin-settings'; import { getSetting, ORDER_STATUSES } from '@woocommerce/wc-admin-settings';
/** /**
* Internal dependencies * Internal dependencies
@ -25,8 +25,8 @@ export const DEFAULT_ORDER_STATUSES = [
export const DEFAULT_DATE_RANGE = 'period=month&compare=previous_year'; export const DEFAULT_DATE_RANGE = 'period=month&compare=previous_year';
const filteredOrderStatuses = Object.keys( ORDER_STATUSES ) const filteredOrderStatuses = Object.keys( ORDER_STATUSES )
.filter( status => status !== 'refunded' ) .filter( ( status ) => status !== 'refunded' )
.map( key => { .map( ( key ) => {
return { return {
value: key, value: key,
label: ORDER_STATUSES[ key ], label: ORDER_STATUSES[ key ],
@ -37,25 +37,46 @@ const filteredOrderStatuses = Object.keys( ORDER_STATUSES )
}; };
} ); } );
const unregisteredOrderStatuses = getSetting( 'unregisteredOrderStatuses', {} );
const orderStatusOptions = [
{
key: 'defaultStatuses',
options: filteredOrderStatuses.filter( ( status ) =>
DEFAULT_ORDER_STATUSES.includes( status.value )
),
},
{
key: 'customStatuses',
label: __( 'Custom Statuses', 'woocommerce-admin' ),
options: filteredOrderStatuses.filter(
( status ) => ! DEFAULT_ORDER_STATUSES.includes( status.value )
),
},
{
key: 'unregisteredStatuses',
label: __( 'Unregistered Statuses', 'woocommerce-admin' ),
options: Object.keys( unregisteredOrderStatuses ).map( ( key ) => {
return {
value: key,
label: key,
description: sprintf(
__(
'Exclude the %s status from reports',
'woocommerce-admin'
),
key
),
};
} ),
},
];
export const config = applyFilters( SETTINGS_FILTER, { export const config = applyFilters( SETTINGS_FILTER, {
woocommerce_excluded_report_order_statuses: { woocommerce_excluded_report_order_statuses: {
label: __( 'Excluded Statuses:', 'woocommerce-admin' ), label: __( 'Excluded Statuses:', 'woocommerce-admin' ),
inputType: 'checkboxGroup', inputType: 'checkboxGroup',
options: [ options: orderStatusOptions,
{
key: 'defaultStatuses',
options: filteredOrderStatuses.filter( status =>
DEFAULT_ORDER_STATUSES.includes( status.value )
),
},
{
key: 'customStatuses',
label: __( 'Custom Statuses', 'woocommerce-admin' ),
options: filteredOrderStatuses.filter(
status => ! DEFAULT_ORDER_STATUSES.includes( status.value )
),
},
],
helpText: interpolateComponents( { helpText: interpolateComponents( {
mixedString: __( mixedString: __(
'Orders with these statuses are excluded from the totals in your reports. ' + 'Orders with these statuses are excluded from the totals in your reports. ' +
@ -71,21 +92,7 @@ export const config = applyFilters( SETTINGS_FILTER, {
woocommerce_actionable_order_statuses: { woocommerce_actionable_order_statuses: {
label: __( 'Actionable Statuses:', 'woocommerce-admin' ), label: __( 'Actionable Statuses:', 'woocommerce-admin' ),
inputType: 'checkboxGroup', inputType: 'checkboxGroup',
options: [ options: orderStatusOptions,
{
key: 'defaultStatuses',
options: filteredOrderStatuses.filter( status =>
DEFAULT_ORDER_STATUSES.includes( status.value )
),
},
{
key: 'customStatuses',
label: __( 'Custom Statuses', 'woocommerce-admin' ),
options: filteredOrderStatuses.filter(
status => ! DEFAULT_ORDER_STATUSES.includes( status.value )
),
},
],
helpText: __( helpText: __(
'Orders with these statuses require action on behalf of the store admin.' + 'Orders with these statuses require action on behalf of the store admin.' +
'These orders will show up in the Orders tab under the activity panel.', 'These orders will show up in the Orders tab under the activity panel.',

View File

@ -12,6 +12,7 @@ defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore; use \Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface; use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery; use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use \Automattic\WooCommerce\Admin\API\Reports\Cache;
/** /**
* API\Reports\Orders\DataStore. * API\Reports\Orders\DataStore.
@ -432,6 +433,29 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
return $coupons; return $coupons;
} }
/**
* Get all statuses that have been synced.
*
* @return array Unique order statuses.
*/
public static function get_all_statuses() {
global $wpdb;
$cache_key = 'orders-all-statuses';
$statuses = Cache::get( $cache_key );
if ( false === $statuses ) {
$table_name = self::get_db_table_name();
$statuses = $wpdb->get_col(
"SELECT DISTINCT status FROM {$table_name}"
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
Cache::set( $cache_key, $statuses );
}
return $statuses;
}
/** /**
* Initialize query objects. * Initialize query objects.
*/ */

View File

@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Admin;
use \_WP_Dependency; use \_WP_Dependency;
use Automattic\WooCommerce\Admin\Features\Onboarding; use Automattic\WooCommerce\Admin\Features\Onboarding;
use Automattic\WooCommerce\Admin\API\Reports\Orders\DataStore as OrdersDataStore;
/** /**
* Loader Class. * Loader Class.
@ -73,8 +74,6 @@ class Loader {
add_action( 'in_admin_header', array( __CLASS__, 'embed_page_header' ) ); add_action( 'in_admin_header', array( __CLASS__, 'embed_page_header' ) );
add_filter( 'woocommerce_settings_groups', array( __CLASS__, 'add_settings_group' ) ); add_filter( 'woocommerce_settings_groups', array( __CLASS__, 'add_settings_group' ) );
add_filter( 'woocommerce_settings-wc_admin', array( __CLASS__, 'add_settings' ) ); add_filter( 'woocommerce_settings-wc_admin', array( __CLASS__, 'add_settings' ) );
add_filter( 'option_woocommerce_actionable_order_statuses', array( __CLASS__, 'filter_invalid_statuses' ) );
add_filter( 'option_woocommerce_excluded_report_order_statuses', array( __CLASS__, 'filter_invalid_statuses' ) );
add_action( 'admin_head', array( __CLASS__, 'remove_notices' ) ); add_action( 'admin_head', array( __CLASS__, 'remove_notices' ) );
add_action( 'admin_notices', array( __CLASS__, 'inject_before_notices' ), -9999 ); add_action( 'admin_notices', array( __CLASS__, 'inject_before_notices' ), -9999 );
add_action( 'admin_notices', array( __CLASS__, 'inject_after_notices' ), PHP_INT_MAX ); add_action( 'admin_notices', array( __CLASS__, 'inject_after_notices' ), PHP_INT_MAX );
@ -724,6 +723,9 @@ class Loader {
// WooCommerce Branding is an example of this - so pass through the translation of // WooCommerce Branding is an example of this - so pass through the translation of
// 'WooCommerce' to wcSettings. // 'WooCommerce' to wcSettings.
$settings['woocommerceTranslation'] = __( 'WooCommerce', 'woocommerce-admin' ); $settings['woocommerceTranslation'] = __( 'WooCommerce', 'woocommerce-admin' );
// We may have synced orders with a now-unregistered status.
// E.g An extension that added statuses is now inactive or removed.
$settings['unregisteredOrderStatuses'] = self::get_unregistered_order_statuses();
if ( ! empty( $preload_data_endpoints ) ) { if ( ! empty( $preload_data_endpoints ) ) {
$settings['dataEndpoints'] = isset( $settings['dataEndpoints'] ) $settings['dataEndpoints'] = isset( $settings['dataEndpoints'] )
@ -760,6 +762,21 @@ class Loader {
return $formatted_statuses; return $formatted_statuses;
} }
/**
* Get all order statuses present in analytics tables that aren't registered.
*
* @return array Unregistered order statuses.
*/
public static function get_unregistered_order_statuses() {
$registered_statuses = wc_get_order_statuses();
$all_synced_statuses = OrdersDataStore::get_all_statuses();
$unregistered_statuses = array_diff( $all_synced_statuses, array_keys( $registered_statuses ) );
$formatted_status_keys = self::get_order_statuses( array_fill_keys( $unregistered_statuses, '' ) );
$formatted_statuses = array_keys( $formatted_status_keys );
return array_combine( $formatted_statuses, $formatted_statuses );
}
/** /**
* Register the admin settings for use in the WC REST API * Register the admin settings for use in the WC REST API
* *
@ -782,7 +799,10 @@ class Loader {
* @return array * @return array
*/ */
public static function add_settings( $settings ) { public static function add_settings( $settings ) {
$statuses = self::get_order_statuses( wc_get_order_statuses() ); $unregistered_statuses = self::get_unregistered_order_statuses();
$registered_statuses = self::get_order_statuses( wc_get_order_statuses() );
$all_statuses = array_merge( $unregistered_statuses, $registered_statuses );
$settings[] = array( $settings[] = array(
'id' => 'woocommerce_excluded_report_order_statuses', 'id' => 'woocommerce_excluded_report_order_statuses',
'option_key' => 'woocommerce_excluded_report_order_statuses', 'option_key' => 'woocommerce_excluded_report_order_statuses',
@ -790,7 +810,7 @@ class Loader {
'description' => __( 'Statuses that should not be included when calculating report totals.', 'woocommerce-admin' ), 'description' => __( 'Statuses that should not be included when calculating report totals.', 'woocommerce-admin' ),
'default' => array( 'pending', 'cancelled', 'failed' ), 'default' => array( 'pending', 'cancelled', 'failed' ),
'type' => 'multiselect', 'type' => 'multiselect',
'options' => $statuses, 'options' => $all_statuses,
); );
$settings[] = array( $settings[] = array(
'id' => 'woocommerce_actionable_order_statuses', 'id' => 'woocommerce_actionable_order_statuses',
@ -799,7 +819,7 @@ class Loader {
'description' => __( 'Statuses that require extra action on behalf of the store admin.', 'woocommerce-admin' ), 'description' => __( 'Statuses that require extra action on behalf of the store admin.', 'woocommerce-admin' ),
'default' => array( 'processing', 'on-hold' ), 'default' => array( 'processing', 'on-hold' ),
'type' => 'multiselect', 'type' => 'multiselect',
'options' => $statuses, 'options' => $all_statuses,
); );
$settings[] = array( $settings[] = array(
'id' => 'woocommerce_default_date_range', 'id' => 'woocommerce_default_date_range',
@ -812,21 +832,6 @@ class Loader {
return $settings; return $settings;
} }
/**
* Filter invalid statuses from saved settings to avoid removed statuses throwing errors.
*
* @param array|null $value Saved order statuses.
* @return array|null
*/
public static function filter_invalid_statuses( $value ) {
if ( is_array( $value ) ) {
$valid_statuses = array_keys( self::get_order_statuses( wc_get_order_statuses() ) );
$value = array_intersect( $value, $valid_statuses );
}
return $value;
}
/** /**
* Gets custom settings used for WC Admin. * Gets custom settings used for WC Admin.
* *