Reporting Engines Overview

The system implements two different reporting engines for handling large dataset processing: Action Scheduler based and Asynchronous Request based.

The use of two different engines is due to the long timeline of the development. Whilst they have some different use case advantages, current development should be based on the Action Scheduler based engine. The action scheduler is now an integral part of WooCommerce, and also allows for a simpler more consolidated build (single class, rather the Asynchronous Request appraoch that requires a controller and a separate request class).

Aside: we also have a small number of older utilities based on third approach that uses the WP_Background_Process system. To be documented separately.

Ideally, we’d move all reporting to the Action Scheduler based engine.

Action Scheduler Based Engine (JC_AS_Report_Controller)

Key Characteristics

  • Built on WordPress Action Scheduler
  • Queued processing with scheduled intervals
  • Better for resource-intensive tasks
  • Supports concurrent report generation
  • Built-in retry mechanism for failed jobs
  • Progress tracking through database

Components

  1. JC_AS_Report_Controller

    • Base abstract controller class
    • Handles report lifecycle
    • Manages AJAX endpoints
    • Controls report status and progress
  2. JC_Async_Report

    • Data model for report instances
    • Tracks metadata and state
    • Handles file downloads

Use Cases

  • Large dataset processing
  • Resource-intensive calculations
  • Reports requiring multiple processing stages
  • When job queuing is beneficial
  • When automatic retry on failure is needed

Asynchronous Request Based Engine (JC_AR_Report_Controller)

Key Characteristics

  • Uses direct async HTTP requests
  • Immediate processing without queuing
  • Faster for smaller datasets
  • Simpler implementation
  • Manual control over retry logic
  • Lighter weight

Components

  1. JC_AR_Report_Controller
    • Base abstract controller
    • Handles report initialization
    • Controls async processing
    • Manages file generation
  2. JC_Async_Report_Request
    • Handles individual processing steps
    • Manages data fetching and processing
    • Controls batch sizes
  3. JC_Async_Request
    • Base request handling
    • Manages HTTP requests
    • Handles authentication

Use Cases

  • Smaller datasets
  • When immediate processing is preferred
  • Simpler report generation
  • When queuing is not required
  • Direct control over processing flow

Key Differences

  1. Processing Model
    • Action Scheduler: Queued, scheduled processing
    • Async Request: Immediate, direct processing
  2. Resource Management
    • Action Scheduler: Better resource distribution
    • Async Request: More immediate resource usage
  3. Reliability
    • Action Scheduler: Built-in retry mechanism
    • Async Request: Manual retry handling required
  4. Complexity
    • Action Scheduler: More complex setup
    • Async Request: Simpler implementation
  5. Concurrency
    • Action Scheduler: Built-in support
    • Async Request: Limited by server configuration

Implementation Examples

Action Scheduler Based

class JC_BGB_Shipping_Controller extends JC_AS_Report_Controller {

    public static $instance;

    /**
     * Initialize the class and set its properties.
     *
     * @since    1.0.0
     */
    public function __construct() {

        $this->report_type = 'bgb-shipping';
        $this->action = 'bgb-shipping';
        $this->base_action = 'bgb_async_shipping';
        $this->supports_concurrent = false;
        $this->per_step = 10;
        $this->log_context['source'] = 'BGB Shipping';
 
        parent::__construct();
    }

    //
}

$downloader = JC_BGB_Shipping_Controller::get_instance();

Async Request Based

class JC_Marketing_Report_Controller extends JC_AR_Report_Controller {
    protected $report_type = 'marketing-report';
    protected $action = 'marketing-report';
    protected $base_action = 'async_marketing_report';
    protected $async_request;
    
    public function __construct() {
        $this->report_type = 'marketing-report';
        $this->action = 'marketing-report';
        $this->base_action = 'async_marketing_report';
        $this->async_request = JC_Registry::get_instance('marketing-report');
        
        parent::__construct();
    }

    protected function setup_context(JC_Async_Report &$report, array $context) {
        // Extract the filter terms
        $context['filter'] = $this->extract_filter($context);
        $report->set_context(json_encode($context));

        $file = $this->create_file($context);
        $report->set_downloads(json_encode([$file]));
    }

    protected function get_total_steps(JC_Async_Report &$report) : int {
        return 0; // Dynamic calculation based on data
    }
}
class JC_Marketing_Report_Request extends JC_Async_Report_Request {
    protected $action = "marketing_report_request";
    protected $context = ['source' => 'JC Marketing Report'];

    protected function fetch_data(JC_Async_Report $report, int $step) : array {
        $context = json_decode($report->get_context(), true);
        return WC_Data_Store::load('tsj_subscription')
            ->get_subscription_ids($step * $this->per_step, $this->per_step, $context['filter']);
    }

    protected function process_data(array $data, JC_Async_Report $report, int $step) : bool {
        $downloads = json_decode($report->get_downloads());
        $fp = fopen($downloads[0]->filename, 'a');
        
        foreach($data as $subscription_id) {
            $subscription = tsj_subscriptions_get_subscription($subscription_id);
            // Process subscription marketing data
            // Write to CSV file
        }
        
        fclose($fp);
        return true;
    }
}