From f9cf25e8d79b26c296a3483dd45eae612ed8d3d1 Mon Sep 17 00:00:00 2001 From: Eason Date: Mon, 23 Sep 2024 17:08:47 +0800 Subject: [PATCH] Legacy reports: Decouple the dependency between the legacy reports and the WooCommerce Status widget on the dashboard page (#51566) * Reverse the dependency on drawing sparklines in the legacy reports from the WooCommerce Status widget on the dashboard page. * Add a new class to simulate the loading plugin for the legacy reports. * Decouple the dependency between the legacy reports and the WooCommerce Status widget on the dashboard page. * Break down the method of forming HTML for the sparkline in Dashboard widget to avoid directly outputting HTML via a filtered callback. * Update PHP unit tests for WC_Tests_Admin_Dashboard. * Add changelog. * Ensure the backward compatibility for the `woocommerce_after_dashboard_status_widget ` action. Address: https://github.com/woocommerce/woocommerce/pull/51566#discussion_r1768751063 * Fix the incorrect indentation of a modified line of codes in the `WC_Admin_Dashboard` class. Address: https://github.com/woocommerce/woocommerce/pull/51566#discussion_r1768690182 * Fix a PHP 8 deprecation message due to a required parameter after an optional parameter in the `WC_Admin_Dashboard` class. Address: https://github.com/woocommerce/woocommerce/pull/51566#discussion_r1768776896 * Tweak the inaccurate PHPDoc for the `get_sales_sparkline ` method in the `WC_Admin_Report` class. Address: https://github.com/woocommerce/woocommerce/pull/51566#discussion_r1768804777 --- ...legacy-reports-and-dashboard-status-widget | 4 + .../client/legacy/js/admin/reports.js | 54 ------- .../legacy/js/admin/wc-status-widget.js | 58 ++++++++ .../includes/admin/class-wc-admin-assets.php | 2 +- .../admin/class-wc-admin-dashboard.php | 139 +++++++++++------- .../includes/admin/class-wc-admin-reports.php | 44 ++++++ .../admin/reports/class-wc-admin-report.php | 34 ++++- .../admin/woocommerce-legacy-reports.php | 20 +++ .../includes/class-woocommerce.php | 3 + .../admin/class-wc-tests-admin-dashboard.php | 26 ++++ .../class-wc-tests-report-sales-by-date.php | 2 +- 11 files changed, 268 insertions(+), 118 deletions(-) create mode 100644 plugins/woocommerce/changelog/tweak-decouple-legacy-reports-and-dashboard-status-widget create mode 100644 plugins/woocommerce/includes/admin/woocommerce-legacy-reports.php diff --git a/plugins/woocommerce/changelog/tweak-decouple-legacy-reports-and-dashboard-status-widget b/plugins/woocommerce/changelog/tweak-decouple-legacy-reports-and-dashboard-status-widget new file mode 100644 index 00000000000..0180c471b4e --- /dev/null +++ b/plugins/woocommerce/changelog/tweak-decouple-legacy-reports-and-dashboard-status-widget @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Decouple the dependency between the WooCommerce legacy reports and the WooCommerce Status widget on the dashboard page. diff --git a/plugins/woocommerce/client/legacy/js/admin/reports.js b/plugins/woocommerce/client/legacy/js/admin/reports.js index 8f3857aef01..598e44142f5 100644 --- a/plugins/woocommerce/client/legacy/js/admin/reports.js +++ b/plugins/woocommerce/client/legacy/js/admin/reports.js @@ -50,60 +50,6 @@ jQuery(function( $ ) { } }); - $( '.wc_sparkline.bars' ).each( function() { - var chart_data = $( this ).data( 'sparkline' ); - - var options = { - grid: { - show: false - } - }; - - // main series - var series = [{ - data: chart_data, - color: $( this ).data( 'color' ), - bars: { - fillColor: $( this ).data( 'color' ), - fill: true, - show: true, - lineWidth: 1, - barWidth: $( this ).data( 'barwidth' ), - align: 'center' - }, - shadowSize: 0 - }]; - - // draw the sparkline - $.plot( $( this ), series, options ); - }); - - $( '.wc_sparkline.lines' ).each( function() { - var chart_data = $( this ).data( 'sparkline' ); - - var options = { - grid: { - show: false - } - }; - - // main series - var series = [{ - data: chart_data, - color: $( this ).data( 'color' ), - lines: { - fill: false, - show: true, - lineWidth: 1, - align: 'center' - }, - shadowSize: 0 - }]; - - // draw the sparkline - $.plot( $( this ), series, options ); - }); - var dates = $( '.range_datepicker' ).datepicker({ changeMonth: true, changeYear: true, diff --git a/plugins/woocommerce/client/legacy/js/admin/wc-status-widget.js b/plugins/woocommerce/client/legacy/js/admin/wc-status-widget.js index ec32e7f43bf..d3c4ba92f31 100644 --- a/plugins/woocommerce/client/legacy/js/admin/wc-status-widget.js +++ b/plugins/woocommerce/client/legacy/js/admin/wc-status-widget.js @@ -33,4 +33,62 @@ $( '.out-of-stock a' ).on( 'click', function() { recordEvent( 'out-of-stock' ); }); + + $( '.wc_sparkline.bars' ).each( function () { + const chartData = $( this ).data( 'sparkline' ); + + const options = { + grid: { + show: false, + }, + }; + + // main series + const series = [ + { + data: chartData, + color: $( this ).data( 'color' ), + bars: { + fillColor: $( this ).data( 'color' ), + fill: true, + show: true, + lineWidth: 1, + barWidth: $( this ).data( 'barwidth' ), + align: 'center', + }, + shadowSize: 0, + }, + ]; + + // draw the sparkline + $.plot( $( this ), series, options ); + } ); + + $( '.wc_sparkline.lines' ).each( function () { + const chartData = $( this ).data( 'sparkline' ); + + const options = { + grid: { + show: false, + }, + }; + + // main series + const series = [ + { + data: chartData, + color: $( this ).data( 'color' ), + lines: { + fill: false, + show: true, + lineWidth: 1, + align: 'center', + }, + shadowSize: 0, + }, + ]; + + // draw the sparkline + $.plot( $( this ), series, options ); + } ); })( jQuery ); diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php index f93b755c6c1..78f76729f2d 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php @@ -472,7 +472,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) : // Reports Pages. /* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */ - if ( in_array( $screen_id, apply_filters( 'woocommerce_reports_screen_ids', array( $wc_screen_id . '_page_wc-reports', 'toplevel_page_wc-reports', 'dashboard' ) ) ) ) { + if ( in_array( $screen_id, apply_filters( 'woocommerce_reports_screen_ids', array( $wc_screen_id . '_page_wc-reports', 'toplevel_page_wc-reports' ) ) ) ) { wp_register_script( 'wc-reports', WC()->plugin_url() . '/assets/js/admin/reports' . $suffix . '.js', array( 'jquery', 'jquery-ui-datepicker' ), $version ); wp_enqueue_script( 'wc-reports' ); diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php index 0077c5500b8..1daf35e0a41 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-dashboard.php @@ -127,23 +127,6 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : return $wpdb->get_row( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } - /** - * Get sales report data. - * - * @return object - */ - private function get_sales_report_data() { - include_once dirname( __FILE__ ) . '/reports/class-wc-report-sales-by-date.php'; - - $sales_by_date = new WC_Report_Sales_By_Date(); - $sales_by_date->start_date = strtotime( gmdate( 'Y-m-01', current_time( 'timestamp' ) ) ); - $sales_by_date->end_date = strtotime( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) ); - $sales_by_date->chart_groupby = 'day'; - $sales_by_date->group_by_query = 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)'; - - return $sales_by_date->get_report_data(); - } - /** * Show status widget. */ @@ -151,32 +134,61 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; $version = Constants::get_constant( 'WC_VERSION' ); - wp_enqueue_script( 'wc-status-widget', WC()->plugin_url() . '/assets/js/admin/wc-status-widget' . $suffix . '.js', array( 'jquery' ), $version, true ); - - include_once dirname( __FILE__ ) . '/reports/class-wc-admin-report.php'; + wp_enqueue_script( 'wc-status-widget', WC()->plugin_url() . '/assets/js/admin/wc-status-widget' . $suffix . '.js', array( 'jquery', 'flot' ), $version, true ); //phpcs:ignore $is_wc_admin_disabled = apply_filters( 'woocommerce_admin_disabled', false ) || ! Features::is_enabled( 'analytics' ); - $reports = new WC_Admin_Report(); + $status_widget_reports = array( + 'net_sales_link' => 'admin.php?page=wc-admin&path=%2Fanalytics%2Frevenue&chart=net_revenue&orderby=net_revenue&period=month&compare=previous_period', + 'top_seller_link' => 'admin.php?page=wc-admin&filter=single_product&path=%2Fanalytics%2Fproducts&products=', + 'lowstock_link' => 'admin.php?page=wc-admin&type=lowstock&path=%2Fanalytics%2Fstock', + 'outofstock_link' => 'admin.php?page=wc-admin&type=outofstock&path=%2Fanalytics%2Fstock', + 'report_data' => null, + 'get_sales_sparkline' => array( $this, 'get_sales_sparkline' ), + ); - $net_sales_link = 'admin.php?page=wc-reports&tab=orders&range=month'; - $top_seller_link = 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids='; - $report_data = $is_wc_admin_disabled ? $this->get_sales_report_data() : $this->get_wc_admin_performance_data(); - if ( ! $is_wc_admin_disabled ) { - $net_sales_link = 'admin.php?page=wc-admin&path=%2Fanalytics%2Frevenue&chart=net_revenue&orderby=net_revenue&period=month&compare=previous_period'; - $top_seller_link = 'admin.php?page=wc-admin&filter=single_product&path=%2Fanalytics%2Fproducts&products='; + if ( $is_wc_admin_disabled ) { + /** + * Filter to change the reports of the status widget on the Dashboard page. + * + * Please note that this filter is mainly for backward compatibility with the legacy reports. + * It's not recommended to use this filter to change the data of this widget. + * + * @since 9.5.0 + */ + $status_widget_reports = apply_filters( 'woocommerce_dashboard_status_widget_reports', $status_widget_reports ); + } else { + $status_widget_reports['report_data'] = $this->get_wc_admin_performance_data(); } echo ''; } @@ -262,9 +285,10 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : /** * Show stock data is status widget. * - * @param bool $is_wc_admin_disabled if woocommerce admin is disabled. + * @param string $lowstock_link Low stock link. + * @param string $outofstock_link Out of stock link. */ - private function status_widget_stock_rows( $is_wc_admin_disabled ) { + private function status_widget_stock_rows( $lowstock_link, $outofstock_link ) { global $wpdb; // Requires lookup table added in 3.6. @@ -309,13 +333,8 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : $transient_name = 'wc_outofstock_count'; $outofstock_count = get_transient( $transient_name ); - $lowstock_link = 'admin.php?page=wc-reports&tab=stock&report=low_in_stock'; - $outofstock_link = 'admin.php?page=wc-reports&tab=stock&report=out_of_stock'; - - if ( false === $is_wc_admin_disabled ) { - $lowstock_link = 'admin.php?page=wc-admin&type=lowstock&path=%2Fanalytics%2Fstock'; - $outofstock_link = 'admin.php?page=wc-admin&type=outofstock&path=%2Fanalytics%2Fstock'; - } + $lowstock_url = $lowstock_link ? admin_url( $lowstock_link ) : '#'; + $outofstock_url = $outofstock_link ? admin_url( $outofstock_link ) : '#'; if ( false === $outofstock_count ) { /** @@ -344,7 +363,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : } ?>
  • - +
  • - + sales_sparkline( $id, $days, $type ); - } + private function get_sales_sparkline( $id = '', $days = 7, $type = 'sales' ) { $sales_endpoint = '/wc-analytics/reports/revenue/stats'; $start_date = gmdate( 'Y-m-d 00:00:00', current_time( 'timestamp' ) - ( ( $days - 1 ) * DAY_IN_SECONDS ) ); $end_date = gmdate( 'Y-m-d 23:59:59', current_time( 'timestamp' ) ); @@ -576,6 +589,22 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : array_push( $sparkline_data, array( strval( strtotime( $d['interval'] ) * 1000 ), $d['subtotals']->$meta_key ) ); } + return array( + 'total' => $total, + 'data' => $sparkline_data, + ); + } + + /** + * Prepares the markup for a sparkline to show sales in the last X days with the given data. + * + * @param string $type Type of sparkline to form the markup. + * @param int $days Days of stats to form the markup. + * @param int $total Total income or items sold to form the markup. + * @param array $sparkline_data Sparkline data to form the markup. + * @return string + */ + private function sales_sparkline_markup( $type, $days, $total, $sparkline_data ) { if ( 'sales' === $type ) { /* translators: 1: total income 2: days */ $tooltip = sprintf( __( 'Sold %1$s worth in the last %2$d days', 'woocommerce' ), strip_tags( wc_price( $total ) ), $days ); diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-reports.php b/plugins/woocommerce/includes/admin/class-wc-admin-reports.php index 0d5dc4372e5..998d007eee4 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-reports.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-reports.php @@ -23,6 +23,50 @@ if ( class_exists( 'WC_Admin_Reports', false ) ) { */ class WC_Admin_Reports { + /** + * Register the proper hook handlers. + */ + public static function register_hook_handlers() { + add_filter( 'woocommerce_after_dashboard_status_widget_parameter', array( __CLASS__, 'get_report_instance' ) ); + add_filter( 'woocommerce_dashboard_status_widget_reports', array( __CLASS__, 'replace_dashboard_status_widget_reports' ) ); + } + + /** + * Get an instance of WC_Admin_Report. + * + * @return WC_Admin_Report + */ + public static function get_report_instance() { + include_once __DIR__ . '/reports/class-wc-admin-report.php'; + return new WC_Admin_Report(); + } + + /** + * Filter handler for replacing the data of the status widget on the Dashboard page. + * + * @param array $status_widget_reports The data to display in the status widget. + */ + public static function replace_dashboard_status_widget_reports( $status_widget_reports ) { + $report = self::get_report_instance(); + + include_once __DIR__ . '/reports/class-wc-report-sales-by-date.php'; + + $sales_by_date = new WC_Report_Sales_By_Date(); + $sales_by_date->start_date = strtotime( gmdate( 'Y-m-01', current_time( 'timestamp' ) ) ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested + $sales_by_date->end_date = strtotime( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested + $sales_by_date->chart_groupby = 'day'; + $sales_by_date->group_by_query = 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)'; + + $status_widget_reports['net_sales_link'] = 'admin.php?page=wc-reports&tab=orders&range=month'; + $status_widget_reports['top_seller_link'] = 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids='; + $status_widget_reports['lowstock_link'] = 'admin.php?page=wc-reports&tab=stock&report=low_in_stock'; + $status_widget_reports['outofstock_link'] = 'admin.php?page=wc-reports&tab=stock&report=out_of_stock'; + $status_widget_reports['report_data'] = $sales_by_date->get_report_data(); + $status_widget_reports['get_sales_sparkline'] = array( $report, 'get_sales_sparkline' ); + + return $status_widget_reports; + } + /** * Handles output of the reports page in admin. */ diff --git a/plugins/woocommerce/includes/admin/reports/class-wc-admin-report.php b/plugins/woocommerce/includes/admin/reports/class-wc-admin-report.php index bc1f7837cc7..a1d808488c0 100644 --- a/plugins/woocommerce/includes/admin/reports/class-wc-admin-report.php +++ b/plugins/woocommerce/includes/admin/reports/class-wc-admin-report.php @@ -524,14 +524,14 @@ class WC_Admin_Report { } /** - * Prepares a sparkline to show sales in the last X days. + * Prepares the data for a sparkline to show sales in the last X days. * * @param int $id ID of the product to show. Blank to get all orders. - * @param int $days Days of stats to get. + * @param int $days Days of stats to get. Default to 7 days. * @param string $type Type of sparkline to get. Ignored if ID is not set. - * @return string + * @return array */ - public function sales_sparkline( $id = '', $days = 7, $type = 'sales' ) { + public function get_sales_sparkline( $id = '', $days = 7, $type = 'sales' ) { // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.Requested @@ -611,6 +611,28 @@ class WC_Admin_Report { $total += $d->sparkline_value; } + $sparkline_data = array_values( $this->prepare_chart_data( $data, 'post_date', 'sparkline_value', $days - 1, strtotime( 'midnight -' . ( $days - 1 ) . ' days', current_time( 'timestamp' ) ), 'day' ) ); + + // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.Requested + + return array( + 'total' => $total, + 'data' => $sparkline_data, + ); + } + + /** + * Prepares the markup for a sparkline to show sales in the last X days. + * + * @param int $id ID of the product to show. Blank to get all orders. + * @param int $days Days of stats to get. Default to 7 days. + * @param string $type Type of sparkline to get. + * @return string + */ + public function sales_sparkline( $id = '', $days = 7, $type = 'sales' ) { + $sparkline = $this->get_sales_sparkline( $id, $days, $type ); + $total = $sparkline['total']; + if ( 'sales' === $type ) { /* translators: 1: total income 2: days */ $tooltip = sprintf( __( 'Sold %1$s worth in the last %2$d days', 'woocommerce' ), wp_strip_all_tags( wc_price( $total ) ), $days ); @@ -619,11 +641,9 @@ class WC_Admin_Report { $tooltip = sprintf( _n( 'Sold %1$d item in the last %2$d days', 'Sold %1$d items in the last %2$d days', $total, 'woocommerce' ), $total, $days ); } - $sparkline_data = array_values( $this->prepare_chart_data( $data, 'post_date', 'sparkline_value', $days - 1, strtotime( 'midnight -' . ( $days - 1 ) . ' days', current_time( 'timestamp' ) ), 'day' ) ); + $sparkline_data = $sparkline['data']; return ''; - - // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.Requested } /** diff --git a/plugins/woocommerce/includes/admin/woocommerce-legacy-reports.php b/plugins/woocommerce/includes/admin/woocommerce-legacy-reports.php new file mode 100644 index 00000000000..704c6d18514 --- /dev/null +++ b/plugins/woocommerce/includes/admin/woocommerce-legacy-reports.php @@ -0,0 +1,20 @@ +is_request( 'admin' ) ) { include_once WC_ABSPATH . 'includes/admin/class-wc-admin.php'; + // Simulate loading plugin for the legacy reports. + // This will be removed after moving the legacy reports to a separate plugin. + include_once WC_ABSPATH . 'includes/admin/woocommerce-legacy-reports.php'; } // We load frontend includes in the post editor, because they may be invoked via pre-loading of blocks. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php b/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php index 73069b34ec5..308e6add425 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php @@ -23,6 +23,7 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case { // Mock http request to performance endpoint. add_filter( 'rest_pre_dispatch', array( $this, 'mock_rest_responses' ), 10, 3 ); + add_filter( 'woocommerce_dashboard_status_widget_reports', array( $this, 'mock_replace_dashboard_status_widget_reports' ) ); } /** @@ -31,6 +32,7 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case { public function tearDown(): void { parent::tearDown(); remove_filter( 'rest_pre_dispatch', array( $this, 'mock_rest_responses' ), 10 ); + remove_filter( 'woocommerce_dashboard_status_widget_reports', array( $this, 'mock_replace_dashboard_status_widget_reports' ) ); } /** @@ -119,4 +121,28 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case { return $response; } + + /** + * Helper method to replace the data to to display in the status widget. + * + * @param array $status_widget_reports The data to display in the status widget. + */ + public function mock_replace_dashboard_status_widget_reports( $status_widget_reports ) { + $report_data = new stdClass(); + $report_data->net_sales = 123; + + $status_widget_reports['net_sales_link'] = 'admin.php?page=wc-reports&tab=orders&range=month'; + $status_widget_reports['top_seller_link'] = 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids='; + $status_widget_reports['lowstock_link'] = 'admin.php?page=wc-reports&tab=stock&report=low_in_stock'; + $status_widget_reports['outofstock_link'] = 'admin.php?page=wc-reports&tab=stock&report=out_of_stock'; + $status_widget_reports['report_data'] = $report_data; + $status_widget_reports['get_sales_sparkline'] = function () { + return array( + 'total' => 50, + 'data' => array(), + ); + }; + + return $status_widget_reports; + } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php b/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php index b7e602e0c56..0763ec03ab0 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php @@ -83,7 +83,7 @@ class WC_Tests_Report_Sales_By_Date extends WC_Unit_Test_Case { ) ); - // Parameters borrowed from WC_Admin_Dashboard::get_sales_report_data(). + // Parameters borrowed from WC_Admin_Reports::replace_dashboard_status_widget_reports(). $report = new WC_Report_Sales_By_Date(); $report->start_date = strtotime( date( 'Y-m-01', current_time( 'timestamp' ) ) ); $report->end_date = current_time( 'timestamp' );