Skip to content

Commit

Permalink
BB-3277: Fixes bug where multiple batch jobs would only trigger first…
Browse files Browse the repository at this point in the history
… job.

When multiple batch jobs exist, the AJAX controller was only configured to target the very first batch job. We now pass the requested batch name into each AJAX request ensuring that the correct job is targeted and run.
  • Loading branch information
Philip Downer committed Nov 20, 2018
1 parent cd3caf2 commit 9896745
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 39 deletions.
49 changes: 23 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ if( is_admin() && class_exists('RamseySolutions\RamseyBatch\Controllers\BatchCon
### Create a batch job class and register the job
Begin by creating a class for your batch job with a basic `__construct` method. Your class _must_ extend the `\RamseySolutions\RamseyBatch\BatchJob` class.

The filter `ramsey-batch-jobs` allows you to hook a class method to register your batch job into the UI.

```php
<?php
namespace MyAppNamespace\Batch;
Expand All @@ -83,35 +81,34 @@ class MyBatchJob extends \RamseySolutions\RamseyBatch\BatchJob {
public function __construct() {
parent::__construct();

add_filter('ramsey-batch-jobs', [$this, 'registerBatchJob']);
}

/**
* Register the batch job
* @param array $jobs Array of batch job information
* @return array
*/
public function registerBatchJob(array $jobs) {
$jobs[__CLASS__] = [
'name' => 'My Batch Job Name',
'description' => 'Description of what the batch job does.',
'lastRunDate' => $this->getLastRunDate()
];

return $jobs;
//Add any logic specific to the class after calling the parent constructor
}
}
```

There are a few methods that your class must provide. The general flow of plugin is:

1. **run()** method is called - This should update the last run date and compile your list of items to be batch processed one-by-one. It should output a JSON array of items if successful.
1. **runItem()** method is called for each item in your collection. Each item from the JSON array is passed to the method individually for processing. If the item is processed successfully, it should [output a successful response](https://codex.wordpress.org/Function_Reference/wp_send_json_success) via JSON.

The general flow of plugin is:
1. The user clicks the "Run Job" button in the UI.
1. The `run()` method of your class is called. This should update the last run date and compile your list of items to be batch processed one-by-one. It should output a JSON array of items if successful.
1. Once all of the batch items have been collected, the `runItem()` method of your class is called for each item in the batch. Each item from the JSON array is passed to the method individually for processing. If the item is processed successfully, it should [output a successful response](https://codex.wordpress.org/Function_Reference/wp_send_json_success) via JSON.

The required methods are stubbed out below:
Because `\RamseySolutions\RamseyBatch\BatchJob` is an abstract class, there are a few methods that your extending class must define. These required methods are stubbed out below:

```php
/**
* Register the batch job
* @param array $jobs Array of batch job information
* @return array
*/
public function registerBatchJob(array $jobs) {
$jobs[__CLASS__] = [
'name' => 'My Batch Job Name',
'description' => 'Description of what the batch job does.',
'lastRunDate' => $this->getLastRunDate()
];

return $jobs;
}

/**
* Set the batch job items
* @param array $items Array of values to process over. These values will be passed into your processing function one-by-one via AJAX. Use the 'run' method to compile
Expand Down Expand Up @@ -155,6 +152,8 @@ public function runItem() {
$postId = $_REQUEST['item'];

//Setup an empty array to hold our response. As batch jobs should only be run in the WP admin by authenticated users, you can use this response to output information in the browser console.
//You can pass any number of elements in the response array, however, the response array expects at least a 'reason' key to provide some browser output.
//You can add a 'type' key with a value of 'warn' to output the message in the browser using console.warn() rather than console.log()
$response = [];

//We're only expecting post ID's, so let's do a quick sanity check. If this fails in real life, you should probably log it out for further analysis.
Expand Down Expand Up @@ -204,8 +203,6 @@ class MyBatchJob extends \RamseySolutions\RamseyBatch\BatchJob {
*/
public function __construct() {
parent::__construct();

add_filter('ramsey-batch-jobs', [$this, 'registerBatchJob']);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Perform batch jobs inside of WordPress.",
"type": "wordpress-plugin",
"license": "GPL v3",
"version": "1.0.1",
"version": "1.1.0",
"authors": [
{
"name": "Philip Downer",
Expand Down
20 changes: 13 additions & 7 deletions js/src/ramsey-batch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
jQuery( document ).ready( ( $ ) => {
const ramseyBatch = {};
ramseyBatch.items = [];
ramseyBatch.batchName;
ramseyBatch.totalItems = ramseyBatch.items.length;
ramseyBatch.currentItem = 0;
ramseyBatch.itemsComplete = 0;
Expand Down Expand Up @@ -61,18 +62,23 @@ jQuery( document ).ready( ( $ ) => {
method: 'POST',
data: {
action: 'ramsey-batch-item',
item
item,
batchName: ramseyBatch.batchName
},
success( response ) {
if ( ! response.success ) {
console.log( response.data.reason, response );
console.error( response.data.reason, response );

return reject();
}

ramseyBatch.itemsComplete++;

console.log( response.data.reason, response );
if ( 'warn' == response.data.type ) {
console.warn( response.data.reason, response );
} else {
console.log( response.data.reason, response );
}

return resolve();
}
Expand All @@ -95,8 +101,8 @@ jQuery( document ).ready( ( $ ) => {
function startBatch( trigger ) {
return new Promise( ( resolve, reject ) => {
currentButton = $( trigger );
const batchName = currentButton.data( 'batchName' );
const batchNameClean = batchName.replace( new RegExp( '\\\\', 'g' ), '' );
ramseyBatch.batchName = currentButton.data( 'batchName' );
const batchNameClean = ramseyBatch.batchName.replace( new RegExp( '\\\\', 'g' ), '' );

progressMeter = $( `tr.progressMeter[data-batch-name="${batchNameClean}"]` );
progressBar = progressMeter.find( '.meter' );
Expand All @@ -110,14 +116,14 @@ jQuery( document ).ready( ( $ ) => {
method: 'POST',
data: {
action: 'ramsey-batch',
batchName
batchName: ramseyBatch.batchName
},
success( response ) {
if ( ! response.success ) {
return reject( new Error( response.data.reason ) );
}

console.log( 'Starting batch!', response.data.items );
console.log( 'Starting batch!', batchNameClean, response.data.items );

updateItems( response.data.items );

Expand Down
3 changes: 0 additions & 3 deletions src/BatchJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ abstract public function registerBatchJob(array $jobs);
public function __construct() {
$this->items = [];
$this->lastRunTimes = get_option(RB_PLUGIN_SLUG . '_batch-run-timestamps', []);

add_action('wp_ajax_' . RB_PLUGIN_SLUG, [$this, 'run'] );
add_action('wp_ajax_' . RB_PLUGIN_SLUG . '-item', [$this, 'runItem']);
}

/**
Expand Down
31 changes: 30 additions & 1 deletion src/Controllers/BatchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,31 @@ public function __construct() {
$this->jobs = apply_filters(RB_PLUGIN_SLUG . '-jobs', [], $this);
}

public static function runJob() {
if( !wp_doing_ajax() || $_REQUEST['action'] != RB_PLUGIN_SLUG ) return;

$currentBatchName = stripslashes($_REQUEST['batchName']);
$batchObj = $GLOBALS[RB_PLUGIN_SLUG][$currentBatchName];

if( !$batchObj ) {
wp_send_json_error(['reason' => "Attempted to run job but global object not found - {$currentBatchName}."]);
}

$batchObj->run();
}

public static function runJobItem() {
if( !wp_doing_ajax() || $_REQUEST['action'] != 'ramsey-batch-item' ) return;

$currentBatchName = stripslashes($_REQUEST['batchName']);
$batchObj = $GLOBALS[RB_PLUGIN_SLUG][$currentBatchName];
if( !$batchObj ) {
wp_send_json_error(['reason' => "Attempted to run job item but global object not found - {$currentBatchName}."]);
}

$batchObj->runItem();
}

/**
* Enqueue JS scripts
* @return void
Expand All @@ -34,7 +59,11 @@ public static function enqueueScripts() {

public static function register(string $name) {
if( !class_exists($name) ) return;
new $name;

$obj = new $name;
add_filter('ramsey-batch-jobs', [$obj, 'registerBatchJob']);

$GLOBALS[RB_PLUGIN_SLUG][$name] = $obj;
}

public function getJobs() {
Expand Down
7 changes: 6 additions & 1 deletion wp-ramsey-batch.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Plugin Name: Ramsey Batch
Plugin URI: https://www.daveramsey.com
Description: Provides a framework and UI for running batch jobs inside of WordPress.
Version: 1.0.1
Version: 1.1.0
Author: Philip Downer <philip.downer@daveramsey.com>
Author URI: https://philipdowner.com
License: GPLv3
Expand Down Expand Up @@ -36,6 +36,11 @@ function ramseyBatchDisplayAdminPage() {
$page->display();
}

add_action('admin_init', function() {
add_action('wp_ajax_' . RB_PLUGIN_SLUG, 'RamseySolutions\RamseyBatch\Controllers\BatchController::runJob' );
add_action('wp_ajax_' . RB_PLUGIN_SLUG . '-item', 'RamseySolutions\RamseyBatch\Controllers\BatchController::runJobItem');
});

add_action('admin_enqueue_scripts', function() {
BatchController::enqueueScripts();
});

0 comments on commit 9896745

Please sign in to comment.