Cancel scheduled progress runs when executed manually

[MAILPOET-4977]
This commit is contained in:
Jan Jakes
2024-04-25 16:19:16 +02:00
committed by Aschepikov
parent a1bafad6f1
commit f71993dde9
4 changed files with 73 additions and 0 deletions

View File

@@ -2,6 +2,8 @@
namespace MailPoet\Automation\Engine\Control;
use ActionScheduler_Action;
class ActionScheduler {
private const GROUP_ID = 'mailpoet-automation';
@@ -16,4 +18,13 @@ class ActionScheduler {
public function hasScheduledAction(string $hook, array $args = []): bool {
return as_has_scheduled_action($hook, $args, self::GROUP_ID);
}
/** @return ActionScheduler_Action[] */
public function getScheduledActions(array $args = []): array {
return as_get_scheduled_actions(array_merge($args, ['group' => self::GROUP_ID]));
}
public function unscheduleAction(string $hook, array $args = []): ?int {
return as_unschedule_action($hook, $args, self::GROUP_ID);
}
}

View File

@@ -2,6 +2,7 @@
namespace MailPoet\Automation\Engine\Control;
use ActionScheduler_CanceledAction;
use MailPoet\Automation\Engine\Data\AutomationRunLog;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Hooks;
@@ -35,6 +36,19 @@ class AutomationController {
'step_id' => $stepId,
'run_number' => $runNumber,
];
// if a pending action exists, unschedule it
$this->actionScheduler->unscheduleAction(Hooks::AUTOMATION_STEP, [$args]);
// if an action still exists (pending, in-progress, complete, failed), it's an error
$actions = $this->actionScheduler->getScheduledActions(['hook' => Hooks::AUTOMATION_STEP, 'args' => [$args]]);
$processedActions = array_filter($actions, function ($action) {
return !$action instanceof ActionScheduler_CanceledAction;
});
if (count($processedActions) > 0) {
throw Exceptions::stepActionProcessed($stepId, $runId, $runNumber);
}
$this->actionScheduler->enqueue(Hooks::AUTOMATION_STEP, [$args]);
}
}

View File

@@ -39,6 +39,7 @@ class Exceptions {
private const AUTOMATION_HAS_ACTIVE_RUNS = 'mailpoet_automation_has_active_runs';
private const AUTOMATION_STEP_NOT_STARTED = 'mailpoet_automation_step_not_started';
private const AUTOMATION_STEP_NOT_RUNNING = 'mailpoet_automation_step_not_running';
private const AUTOMATION_STEP_ACTION_PROCESSED = 'mailpoet_automation_step_action_processed';
public function __construct() {
throw new InvalidStateException(
@@ -290,4 +291,11 @@ class Exceptions {
// translators: %1$s is the ID of the automation step, %2$s its current status, %3$d is the automation run ID.
->withMessage(sprintf(__("Automation step with ID '%1\$s' is not running in automation run with ID '%2\$d'. Status: '%3\$s'", 'mailpoet'), $id, $runId, $status));
}
public static function stepActionProcessed(string $id, int $runId, int $runNumber): InvalidStateException {
return InvalidStateException::create()
->withErrorCode(self::AUTOMATION_STEP_ACTION_PROCESSED)
// translators: %1$d is the automation run ID, %2$s is the ID of the automation step, %3$d is the run number.
->withMessage(sprintf(__("Automation run with ID '%1\$d' already has a processed action for step with ID '%2\$s' and run number '%3\$d'.", 'mailpoet'), $runId, $id, $runNumber));
}
}

View File

@@ -2,8 +2,10 @@
namespace MailPoet\Test\Automation\Engine\Control;
use ActionScheduler;
use ActionScheduler_NullSchedule;
use ActionScheduler_Store;
use MailPoet\Automation\Engine\Control\ActionScheduler as AutomationActionScheduler;
use MailPoet\Automation\Engine\Control\AutomationController;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\AutomationRun;
@@ -60,6 +62,40 @@ class AutomationControllerTest extends MailPoetTest {
$controller->enqueueProgress(1, 'abc');
}
public function testItUnschedulesPendingAction(): void {
$this->createAutomationWithStepRunAndLog(AutomationRun::STATUS_RUNNING, AutomationRunLog::STATUS_RUNNING);
$data = ['automation_run_id' => 1, 'step_id' => 'abc', 'run_number' => 2];
$this->scheduleAction(time() + MONTH_IN_SECONDS, $data);
$this->assertCount(1, $this->getActions());
$this->assertCount(1, $this->getActions(['status' => [ActionScheduler_Store::STATUS_PENDING]]));
$controller = $this->getServiceWithOverrides(AutomationController::class, [
// skip creating new action so we can check if the existing one is unscheduled
'actionScheduler' => $this->make(AutomationActionScheduler::class, ['enqueue' => 123]),
]);
$controller->enqueueProgress(1, 'abc');
$this->assertCount(1, $this->getActions());
$this->assertCount(1, $this->getActions(['status' => [ActionScheduler_Store::STATUS_CANCELED]]));
}
public function testItFailsWithExistingAction(): void {
$this->createAutomationWithStepRunAndLog(AutomationRun::STATUS_RUNNING, AutomationRunLog::STATUS_RUNNING);
$data = ['automation_run_id' => 1, 'step_id' => 'abc', 'run_number' => 2];
$actionId = $this->scheduleAction(time() + MONTH_IN_SECONDS, $data);
ActionScheduler::store()->mark_complete($actionId);
$this->assertCount(1, $this->getActions());
$this->assertCount(1, $this->getActions(['status' => [ActionScheduler_Store::STATUS_COMPLETE]]));
$this->expectException(InvalidStateException::class);
$this->expectExceptionMessage("Automation run with ID '1' already has a processed action for step with ID 'abc' and run number '2'.");
$controller = $this->diContainer->get(AutomationController::class);
$controller->enqueueProgress(1, 'abc');
}
private function createAutomationWithStepRunAndLog(string $runStatus, string $logStatus): void {
$step = new Step('abc', Step::TYPE_ACTION, 'key', [], []);
$automation = (new DataFactories\Automation())
@@ -78,6 +114,10 @@ class AutomationControllerTest extends MailPoetTest {
->create();
}
private function scheduleAction(int $timestamp, array $args): int {
return as_schedule_single_action($timestamp, Hooks::AUTOMATION_STEP, [$args], 'mailpoet-automation');
}
private function getActions(array $args = []): array {
return array_values(
as_get_scheduled_actions(