* Add leaderboard controller and endpoint

* Add existing leaderboard stats to leaderboard endpoint

* Add persisted query to leaderboard params

* Add leaderboard endpoint tests

* Check if extended_info params are set before assigning

* Change number param input format to mixed inside wc_admin_number_format
This commit is contained in:
Joshua T Flowers 2019-04-09 11:34:56 +08:00 committed by GitHub
parent 2ec3e9843a
commit f682fe76be
5 changed files with 651 additions and 11 deletions

View File

@ -0,0 +1,482 @@
<?php
/**
* REST API Leaderboards Controller
*
* Handles requests to /leaderboards
*
* @package WooCommerce Admin/API
*/
defined( 'ABSPATH' ) || exit;
/**
* Leaderboards controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Data_Controller
*/
class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v4';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'leaderboards';
/**
* Register routes.
*/
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' ),
)
);
}
/**
* Get the data for the coupons leaderboard.
*
* @param int $per_page Number of rows.
* @param string $after Items after date.
* @param string $before Items before date.
* @param string $persisted_query URL query string.
*/
public function get_coupons_leaderboard( $per_page, $after, $before, $persisted_query ) {
$coupons_data_store = new WC_Admin_Reports_Coupons_Data_Store();
$coupons_data = $coupons_data_store->get_data(
array(
'orderby' => 'orders_count',
'order' => 'desc',
'after' => $after,
'before' => $before,
'per_page' => $per_page,
'extended_info' => true,
)
);
$rows = array();
foreach ( $coupons_data->data as $coupon ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_coupon',
'coupons' => $coupon['coupon_id'],
),
$persisted_query
);
$coupon_url = wc_admin_url( 'analytics/coupons', $url_query );
$coupon_code = isset( $coupon['extended_info'] ) && isset( $coupon['extended_info']['code'] ) ? $coupon['extended_info']['code'] : '';
$rows[] = array(
array(
'display' => "<a href='{$coupon_url}'>{$coupon_code}</a>",
'value' => $coupon_code,
),
array(
'display' => wc_admin_number_format( $coupon['orders_count'] ),
'value' => $coupon['orders_count'],
),
array(
'display' => wc_price( $coupon['amount'] ),
'value' => $coupon['amount'],
),
);
}
return array(
'id' => 'coupons',
'label' => __( 'Top Coupons - Number of Orders', 'woocommerce-admin' ),
'headers' => array(
array(
'label' => __( 'Coupon Code', 'woocommerce-admin' ),
),
array(
'label' => __( 'Orders', 'woocommerce-admin' ),
),
array(
'label' => __( 'Amount Discounted', 'woocommerce-admin' ),
),
),
'rows' => $rows,
);
}
/**
* Get the data for the categories leaderboard.
*
* @param int $per_page Number of rows.
* @param string $after Items after date.
* @param string $before Items before date.
* @param string $persisted_query URL query string.
*/
public function get_categories_leaderboard( $per_page, $after, $before, $persisted_query ) {
$categories_data_store = new WC_Admin_Reports_Categories_Data_Store();
$categories_data = $categories_data_store->get_data(
array(
'orderby' => 'items_sold',
'order' => 'desc',
'after' => $after,
'before' => $before,
'per_page' => $per_page,
'extended_info' => true,
)
);
$rows = array();
foreach ( $categories_data->data as $category ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_category',
'categories' => $category['category_id'],
),
$persisted_query
);
$category_url = wc_admin_url( 'analytics/categories', $url_query );
$category_name = isset( $category['extended_info'] ) && isset( $category['extended_info']['name'] ) ? $category['extended_info']['name'] : '';
$rows[] = array(
array(
'display' => "<a href='{$category_url}'>{$category_name}</a>",
'value' => $category_name,
),
array(
'display' => wc_admin_number_format( $category['items_sold'] ),
'value' => $category['items_sold'],
),
array(
'display' => wc_price( $category['net_revenue'] ),
'value' => $category['net_revenue'],
),
);
}
return array(
'id' => 'categories',
'label' => __( 'Top Categories - Items Sold', 'woocommerce-admin' ),
'headers' => array(
array(
'label' => __( 'Category', 'woocommerce-admin' ),
),
array(
'label' => __( 'Items Sold', 'woocommerce-admin' ),
),
array(
'label' => __( 'Net Revenue', 'woocommerce-admin' ),
),
),
'rows' => $rows,
);
}
/**
* Get the data for the customers leaderboard.
*
* @param int $per_page Number of rows.
* @param string $after Items after date.
* @param string $before Items before date.
* @param string $persisted_query URL query string.
*/
public function get_customers_leaderboard( $per_page, $after, $before, $persisted_query ) {
$customers_data_store = new WC_Admin_Reports_Customers_Data_Store();
$customers_data = $customers_data_store->get_data(
array(
'orderby' => 'total_spend',
'order' => 'desc',
'per_page' => $per_page,
)
);
$rows = array();
foreach ( $customers_data->data as $customer ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_customer',
'customers' => $customer['id'],
),
$persisted_query
);
$customer_url = wc_admin_url( 'analytics/customers', $url_query );
$rows[] = array(
array(
'display' => "<a href='{$customer_url}'>{$customer['name']}</a>",
'value' => $customer['name'],
),
array(
'display' => wc_admin_number_format( $customer['orders_count'] ),
'value' => $customer['orders_count'],
),
array(
'display' => wc_price( $customer['total_spend'] ),
'value' => $customer['total_spend'],
),
);
}
return array(
'id' => 'customers',
'label' => __( 'Top Customers - Total Spend', 'woocommerce-admin' ),
'headers' => array(
array(
'label' => __( 'Customer Name', 'woocommerce-admin' ),
),
array(
'label' => __( 'Orders', 'woocommerce-admin' ),
),
array(
'label' => __( 'Total Spend', 'woocommerce-admin' ),
),
),
'rows' => $rows,
);
}
/**
* Get the data for the products leaderboard.
*
* @param int $per_page Number of rows.
* @param string $after Items after date.
* @param string $before Items before date.
* @param string $persisted_query URL query string.
*/
public function get_products_leaderboard( $per_page, $after, $before, $persisted_query ) {
$products_data_store = new WC_Admin_Reports_Products_Data_Store();
$products_data = $products_data_store->get_data(
array(
'orderby' => 'items_sold',
'order' => 'desc',
'after' => $after,
'before' => $before,
'per_page' => $per_page,
'extended_info' => true,
)
);
$rows = array();
foreach ( $products_data->data as $product ) {
$url_query = wp_parse_args(
array(
'filter' => 'single_product',
'products' => $product['product_id'],
),
$persisted_query
);
$product_url = wc_admin_url( 'analytics/products', $url_query );
$product_name = isset( $product['extended_info'] ) && isset( $product['extended_info']['name'] ) ? $product['extended_info']['name'] : '';
$rows[] = array(
array(
'display' => "<a href='{$product_url}'>{$product_name}</a>",
'value' => $product_name,
),
array(
'display' => wc_admin_number_format( $product['items_sold'] ),
'value' => $product['items_sold'],
),
array(
'display' => wc_price( $product['net_revenue'] ),
'value' => $product['net_revenue'],
),
);
}
return array(
'id' => 'products',
'label' => __( 'Top Products - Items Sold', 'woocommerce-admin' ),
'headers' => array(
array(
'label' => __( 'Product', 'woocommerce-admin' ),
),
array(
'label' => __( 'Items Sold', 'woocommerce-admin' ),
),
array(
'label' => __( 'Net Revenue', 'woocommerce-admin' ),
),
),
'rows' => $rows,
);
}
/**
* Get an array of all leaderboards.
*
* @param int $per_page Number of rows.
* @param string $after Items after date.
* @param string $before Items before date.
* @param string $persisted_query URL query string.
* @return array
*/
public function get_leaderboards( $per_page, $after, $before, $persisted_query ) {
$leaderboards = array(
$this->get_customers_leaderboard( $per_page, $after, $before, $persisted_query ),
$this->get_coupons_leaderboard( $per_page, $after, $before, $persisted_query ),
$this->get_categories_leaderboard( $per_page, $after, $before, $persisted_query ),
$this->get_products_leaderboard( $per_page, $after, $before, $persisted_query ),
);
return apply_filters( 'woocommerce_leaderboards', $leaderboards, $per_page, $after, $before, $persisted_query );
}
/**
* Return all leaderboards.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
parse_str( $request['persisted_query'], $persisted_query );
$leaderboards = $this->get_leaderboards( $request['per_page'], $request['after'], $request['before'], $persisted_query );
$data = array();
if ( ! empty( $leaderboards ) ) {
foreach ( $leaderboards as $leaderboard ) {
$response = $this->prepare_item_for_response( $leaderboard, $request );
$data[] = $this->prepare_response_for_collection( $response );
}
}
return rest_ensure_response( $data );
}
/**
* Prepare the data object for response.
*
* @param object $item Data object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, 'view' );
$response = rest_ensure_response( $data );
/**
* Filter the list returned from the API.
*
* @param WP_REST_Response $response The response object.
* @param array $item The original item.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_leaderboard', $response, $item, $request );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = array();
$params['page'] = array(
'description' => __( 'Current page of the collection.', 'woocommerce-admin' ),
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'minimum' => 1,
);
$params['per_page'] = array(
'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce-admin' ),
'type' => 'integer',
'default' => 5,
'minimum' => 1,
'maximum' => 20,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
);
$params['after'] = array(
'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'woocommerce-admin' ),
'type' => 'string',
'format' => 'date-time',
'validate_callback' => 'rest_validate_request_arg',
);
$params['before'] = array(
'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'woocommerce-admin' ),
'type' => 'string',
'format' => 'date-time',
'validate_callback' => 'rest_validate_request_arg',
);
$params['persisted_query'] = array(
'description' => __( 'URL query to persist across links.', 'woocommerce-admin' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/**
* Get the schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'leaderboard',
'type' => 'object',
'properties' => array(
'id' => array(
'type' => 'string',
'description' => __( 'Leaderboard Name.', 'woocommerce-admin' ),
'context' => array( 'view' ),
'readonly' => true,
),
'headers' => array(
'type' => 'array',
'description' => __( 'Table headers.', 'woocommerce-admin' ),
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'array',
'properties' => array(
'label' => array(
'description' => __( 'Table column header.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
'rows' => array(
'type' => 'array',
'description' => __( 'Table rows.', 'woocommerce-admin' ),
'context' => array( 'view' ),
'readonly' => true,
'items' => array(
'type' => 'array',
'properties' => array(
'display' => array(
'description' => __( 'Table cell display.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'value' => array(
'description' => __( 'Table cell value.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -105,6 +105,7 @@ class WC_Admin_Api_Init {
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-data-controller.php';
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-data-countries-controller.php';
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-data-download-ips-controller.php';
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-leaderboards-controller.php';
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-onboarding-levels-controller.php';
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-orders-controller.php';
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-products-controller.php';
@ -146,6 +147,7 @@ class WC_Admin_Api_Init {
'WC_Admin_REST_Data_Controller',
'WC_Admin_REST_Data_Countries_Controller',
'WC_Admin_REST_Data_Download_Ips_Controller',
'WC_Admin_REST_Leaderboards_Controller',
'WC_Admin_REST_Onboarding_Levels_Controller',
'WC_Admin_REST_Orders_Controller',
'WC_Admin_REST_Products_Controller',

View File

@ -20,7 +20,7 @@ function wc_admin_register_script() {
wp_register_script(
'wc-csv',
wc_admin_url( 'dist/csv-export/index.js' ),
wc_admin_plugin_url( 'dist/csv-export/index.js' ),
array(),
filemtime( wc_admin_dir_path( 'dist/csv-export/index.js' ) ),
true
@ -28,7 +28,7 @@ function wc_admin_register_script() {
wp_register_script(
'wc-currency',
wc_admin_url( 'dist/currency/index.js' ),
wc_admin_plugin_url( 'dist/currency/index.js' ),
array( 'wc-number' ),
filemtime( wc_admin_dir_path( 'dist/currency/index.js' ) ),
true
@ -36,7 +36,7 @@ function wc_admin_register_script() {
wp_register_script(
'wc-navigation',
wc_admin_url( 'dist/navigation/index.js' ),
wc_admin_plugin_url( 'dist/navigation/index.js' ),
array(),
filemtime( wc_admin_dir_path( 'dist/navigation/index.js' ) ),
true
@ -44,7 +44,7 @@ function wc_admin_register_script() {
wp_register_script(
'wc-number',
wc_admin_url( 'dist/number/index.js' ),
wc_admin_plugin_url( 'dist/number/index.js' ),
array(),
filemtime( wc_admin_dir_path( 'dist/number/index.js' ) ),
true
@ -52,7 +52,7 @@ function wc_admin_register_script() {
wp_register_script(
'wc-date',
wc_admin_url( 'dist/date/index.js' ),
wc_admin_plugin_url( 'dist/date/index.js' ),
array( 'wp-date', 'wp-i18n' ),
filemtime( wc_admin_dir_path( 'dist/date/index.js' ) ),
true
@ -60,7 +60,7 @@ function wc_admin_register_script() {
wp_register_script(
'wc-components',
wc_admin_url( 'dist/components/index.js' ),
wc_admin_plugin_url( 'dist/components/index.js' ),
array(
'wp-components',
'wp-data',
@ -80,7 +80,7 @@ function wc_admin_register_script() {
wp_register_script(
WC_ADMIN_APP,
wc_admin_url( "dist/{$entry}/index.js" ),
wc_admin_plugin_url( "dist/{$entry}/index.js" ),
array( 'wc-components', 'wc-navigation', 'wp-date', 'wp-html-entities', 'wp-keycodes', 'wp-i18n' ),
filemtime( wc_admin_dir_path( "dist/{$entry}/index.js" ) ),
true
@ -106,7 +106,7 @@ function wc_admin_register_script() {
wp_register_style(
'wc-components',
wc_admin_url( 'dist/components/style.css' ),
wc_admin_plugin_url( 'dist/components/style.css' ),
array( 'wp-edit-blocks' ),
filemtime( wc_admin_dir_path( 'dist/components/style.css' ) )
);
@ -114,7 +114,7 @@ function wc_admin_register_script() {
wp_register_style(
'wc-components-ie',
wc_admin_url( 'dist/components/ie.css' ),
wc_admin_plugin_url( 'dist/components/ie.css' ),
array( 'wp-edit-blocks' ),
filemtime( wc_admin_dir_path( 'dist/components/ie.css' ) )
);
@ -122,7 +122,7 @@ function wc_admin_register_script() {
wp_register_style(
WC_ADMIN_APP,
wc_admin_url( "dist/{$entry}/style.css" ),
wc_admin_plugin_url( "dist/{$entry}/style.css" ),
array( 'wc-components' ),
filemtime( wc_admin_dir_path( "dist/{$entry}/style.css" ) )
);

View File

@ -23,10 +23,28 @@ function wc_admin_dir_path( $file = '' ) {
*
* @return string Fully qualified URL pointing to the desired file.
*/
function wc_admin_url( $path ) {
function wc_admin_plugin_url( $path ) {
return plugins_url( $path, dirname( __FILE__ ) );
}
/**
* Retrieves a URL to relative path inside WooCommerce admin with
* the provided query parameters.
*
* @param string $path Relative path of the desired page.
* @param array $query Query parameters to append to the path.
*
* @return string Fully qualified URL pointing to the desired path.
*/
function wc_admin_url( $path, $query = array() ) {
if ( ! empty( $query ) ) {
$query_string = http_build_query( $query );
$path = $path . '?' . $query_string;
}
return admin_url( 'admin.php?page=wc-admin#' . $path, dirname( __FILE__ ) );
}
/**
* Returns the current screen ID.
* This is slightly different from WP's get_current_screen, in that it attaches an action,
@ -295,3 +313,20 @@ function wc_admin_is_feature_enabled( $feature ) {
$features = wc_admin_get_feature_config();
return isset( $features[ $feature ] ) && true === $features[ $feature ];
}
/**
* Format a number using the decimal and thousands separator settings in WooCommerce.
*
* @param mixed $number Number to be formatted.
* @return string
*/
function wc_admin_number_format( $number ) {
$currency_settings = wc_admin_currency_settings();
return number_format(
$number,
0,
$currency_settings['decimal_separator'],
$currency_settings['thousand_separator']
);
}

View File

@ -0,0 +1,121 @@
<?php
/**
* Leaderboards REST API Test
*
* @package WooCommerce Admin\Tests\API
*/
/**
* WC Tests API Leaderboards
*/
class WC_Tests_API_Leaderboards extends WC_REST_Unit_Test_Case {
/**
* Endpoints.
*
* @var string
*/
protected $endpoint = '/wc/v4/leaderboards';
/**
* Setup test data. Called before every test.
*/
public function setUp() {
parent::setUp();
$this->user = $this->factory->user->create(
array(
'role' => 'administrator',
)
);
}
/**
* Test that leaderboards are returned by the endpoint.
*/
public function test_get_leaderboards() {
wp_set_current_user( $this->user );
$request = new WP_REST_Request( 'GET', $this->endpoint );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'customers', $data[0]['id'] );
$this->assertEquals( 'coupons', $data[1]['id'] );
$this->assertEquals( 'categories', $data[2]['id'] );
$this->assertEquals( 'products', $data[3]['id'] );
}
/**
* Test reports schema.
*/
public function test_schema() {
wp_set_current_user( $this->user );
$request = new WP_REST_Request( 'OPTIONS', $this->endpoint );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertCount( 3, $properties );
$this->assert_item_schema( $properties );
}
/**
* Asserts the item schema is correct.
*
* @param array $schema Item to check schema.
*/
public function assert_item_schema( $schema ) {
$this->assertArrayHasKey( 'id', $schema );
$this->assertArrayHasKey( 'headers', $schema );
$this->assertArrayHasKey( 'rows', $schema );
$header_properties = $schema['headers']['items']['properties'];
$this->assertCount( 1, $header_properties );
$this->assertArrayHasKey( 'label', $header_properties );
$row_properties = $schema['rows']['items']['properties'];
$this->assertCount( 2, $row_properties );
$this->assertArrayHasKey( 'display', $row_properties );
$this->assertArrayHasKey( 'value', $row_properties );
}
/**
* Test that leaderboards response changes based on applied filters.
*/
public function test_filter_leaderboards() {
wp_set_current_user( $this->user );
add_filter(
'woocommerce_leaderboards',
function( $leaderboards, $per_page, $after, $before, $persisted_query ) {
$leaderboards[] = array(
'id' => 'top_widgets',
'headers' => array(
array(
'label' => 'Widget Link',
),
),
'rows' => array(
array(
'display' => wc_admin_url( 'test/path', $persisted_query ),
'value' => null,
),
),
);
return $leaderboards;
},
10,
5
);
$request = new WP_REST_Request( 'GET', $this->endpoint );
$request->set_query_params( array( 'persisted_query' => 'persisted_param=1' ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$widgets_leaderboard = end( $data );
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'top_widgets', $widgets_leaderboard['id'] );
$this->assertEquals( admin_url( 'admin.php?page=wc-admin#test/path?persisted_param=1' ), $widgets_leaderboard['rows'][0]['display'] );
}
}