Save and load workflows in the new format, update API
[MAILPOET-4523]
This commit is contained in:
committed by
John Oleksowicz
parent
9028ea96ec
commit
1677cc2842
@ -4,6 +4,7 @@ namespace MailPoet\AdminPages\Pages;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use MailPoet\AdminPages\PageRenderer;
|
||||
use MailPoet\Automation\Engine\Data\NextStep;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\Workflow;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
@ -95,8 +96,10 @@ class AutomationEditor {
|
||||
'id' => $step->getId(),
|
||||
'type' => $step->getType(),
|
||||
'key' => $step->getKey(),
|
||||
'next_step_id' => $step->getNextStepId(),
|
||||
'args' => $step->getArgs() ?: new \stdClass(),
|
||||
'args' => $step->getArgs(),
|
||||
'next_steps' => array_map(function (NextStep $nextStep) {
|
||||
return $nextStep->toArray();
|
||||
}, $step->getNextSteps()),
|
||||
];
|
||||
}, $workflow->getSteps()),
|
||||
];
|
||||
|
@ -33,13 +33,6 @@ class UpdateStepsController {
|
||||
if (!$step) {
|
||||
throw Exceptions::workflowStepNotFound($key);
|
||||
}
|
||||
|
||||
return new Step(
|
||||
$data['id'],
|
||||
$data['type'],
|
||||
$data['key'],
|
||||
$data['next_step_id'] ?? null,
|
||||
$data['args'] ?? null
|
||||
);
|
||||
return Step::fromArray($data);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace MailPoet\Automation\Engine\Builder;
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\Workflow;
|
||||
use MailPoet\Automation\Engine\Exceptions;
|
||||
use MailPoet\Automation\Engine\Exceptions\UnexpectedValueException;
|
||||
@ -81,15 +82,17 @@ class UpdateWorkflowController {
|
||||
|
||||
foreach ($steps as $id => $data) {
|
||||
$existingStep = $existingSteps[$id] ?? null;
|
||||
if (
|
||||
!$existingStep
|
||||
|| $data['id'] !== $existingStep->getId()
|
||||
|| $data['type'] !== $existingStep->getType()
|
||||
|| $data['key'] !== $existingStep->getKey()
|
||||
|| $data['next_step_id'] !== $existingStep->getNextStepId()
|
||||
) {
|
||||
if (!$existingStep || !$this->stepChanged(Step::fromArray($data), $existingStep)) {
|
||||
throw Exceptions::workflowStructureModificationNotSupported();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function stepChanged(Step $a, Step $b): bool {
|
||||
$aData = $a->toArray();
|
||||
$bData = $b->toArray();
|
||||
unset($aData['args']);
|
||||
unset($bData['args']);
|
||||
return $aData === $bData;
|
||||
}
|
||||
}
|
||||
|
28
mailpoet/lib/Automation/Engine/Data/NextStep.php
Normal file
28
mailpoet/lib/Automation/Engine/Data/NextStep.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Engine\Data;
|
||||
|
||||
class NextStep {
|
||||
/** @var string */
|
||||
protected $id;
|
||||
|
||||
public function __construct(
|
||||
string $id
|
||||
) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function toArray(): array {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self {
|
||||
return new self($data['id']);
|
||||
}
|
||||
}
|
@ -15,24 +15,28 @@ class Step {
|
||||
/** @var string */
|
||||
private $key;
|
||||
|
||||
/** @var string|null */
|
||||
protected $nextStepId;
|
||||
|
||||
/** @var array */
|
||||
protected $args;
|
||||
|
||||
/** @var NextStep[] */
|
||||
protected $nextSteps;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
* @param NextStep[] $nextSteps
|
||||
*/
|
||||
public function __construct(
|
||||
string $id,
|
||||
string $type,
|
||||
string $key,
|
||||
?string $nextStepId = null,
|
||||
array $args = []
|
||||
array $args,
|
||||
array $nextSteps
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->type = $type;
|
||||
$this->key = $key;
|
||||
$this->nextStepId = $nextStepId;
|
||||
$this->args = $args;
|
||||
$this->nextSteps = $nextSteps;
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
@ -47,12 +51,14 @@ class Step {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function getNextStepId(): ?string {
|
||||
return $this->nextStepId;
|
||||
/** @return NextStep[] */
|
||||
public function getNextSteps(): array {
|
||||
return $this->nextSteps;
|
||||
}
|
||||
|
||||
public function setNextStepId(string $id): void {
|
||||
$this->nextStepId = $id;
|
||||
/** @param NextStep[] $nextSteps */
|
||||
public function setNextSteps(array $nextSteps): void {
|
||||
$this->nextSteps = $nextSteps;
|
||||
}
|
||||
|
||||
public function getArgs(): array {
|
||||
@ -64,8 +70,22 @@ class Step {
|
||||
'id' => $this->id,
|
||||
'type' => $this->type,
|
||||
'key' => $this->key,
|
||||
'next_step_id' => $this->nextStepId,
|
||||
'args' => $this->args,
|
||||
'next_steps' => array_map(function (NextStep $nextStep) {
|
||||
return $nextStep->toArray();
|
||||
}, $this->nextSteps),
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self {
|
||||
return new self(
|
||||
$data['id'],
|
||||
$data['type'],
|
||||
$data['key'],
|
||||
$data['args'],
|
||||
array_map(function (array $nextStep) {
|
||||
return NextStep::fromArray($nextStep);
|
||||
}, $data['next_steps'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +186,9 @@ class Workflow {
|
||||
// TODO: validation
|
||||
$workflow = new self(
|
||||
$data['name'],
|
||||
self::parseSteps(Json::decode($data['steps'])),
|
||||
array_map(function (array $stepData): Step {
|
||||
return Step::fromArray($stepData);
|
||||
}, Json::decode($data['steps'])),
|
||||
new \WP_User((int)$data['author'])
|
||||
);
|
||||
$workflow->id = (int)$data['id'];
|
||||
@ -197,18 +199,4 @@ class Workflow {
|
||||
$workflow->activatedAt = $data['activated_at'] !== null ? new DateTimeImmutable($data['activated_at']) : null;
|
||||
return $workflow;
|
||||
}
|
||||
|
||||
private static function parseSteps(array $data): array {
|
||||
$steps = [];
|
||||
foreach ($data as $step) {
|
||||
$steps[] = new Step(
|
||||
$step['id'],
|
||||
$step['type'],
|
||||
$step['key'],
|
||||
$step['next_step_id'] ?? null,
|
||||
$step['args'] ?? []
|
||||
);
|
||||
}
|
||||
return $steps;
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,11 @@ use MailPoet\Automation\Engine\API\Endpoint;
|
||||
use MailPoet\Automation\Engine\API\Request;
|
||||
use MailPoet\Automation\Engine\API\Response;
|
||||
use MailPoet\Automation\Engine\Builder\UpdateWorkflowController;
|
||||
use MailPoet\Automation\Engine\Data\NextStep;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\Workflow;
|
||||
use MailPoet\Automation\Engine\Validators\WorkflowSchema;
|
||||
use MailPoet\Validator\Builder;
|
||||
use stdClass;
|
||||
|
||||
class WorkflowsPutEndpoint extends Endpoint {
|
||||
/** @var UpdateWorkflowController */
|
||||
@ -29,19 +30,11 @@ class WorkflowsPutEndpoint extends Endpoint {
|
||||
}
|
||||
|
||||
public static function getRequestSchema(): array {
|
||||
$step = Builder::object([
|
||||
'id' => Builder::string()->required(),
|
||||
'type' => Builder::string()->required(),
|
||||
'key' => Builder::string()->required(),
|
||||
'args' => Builder::object(),
|
||||
'next_step_id' => Builder::string()->nullable(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'id' => Builder::integer()->required(),
|
||||
'name' => Builder::string()->minLength(1),
|
||||
'status' => Builder::string(),
|
||||
'steps' => Builder::object()->additionalProperties($step),
|
||||
'steps' => WorkflowSchema::getStepsSchema(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -62,8 +55,10 @@ class WorkflowsPutEndpoint extends Endpoint {
|
||||
'id' => $step->getId(),
|
||||
'type' => $step->getType(),
|
||||
'key' => $step->getKey(),
|
||||
'next_step_id' => $step->getNextStepId(),
|
||||
'args' => $step->getArgs() ?: new stdClass(),
|
||||
'args' => $step->getArgs(),
|
||||
'next_steps' => array_map(function (NextStep $nextStep) {
|
||||
return $nextStep->toArray();
|
||||
}, $step->getNextSteps()),
|
||||
];
|
||||
}, $workflow->getSteps()),
|
||||
];
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Templates;
|
||||
|
||||
use MailPoet\Automation\Engine\Data\NextStep;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\Workflow;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
@ -22,7 +23,7 @@ class WorkflowBuilder {
|
||||
|
||||
public function createFromSequence(string $name, array $sequence, array $sequenceArgs = []): Workflow {
|
||||
$steps = [];
|
||||
$nextStep = null;
|
||||
$nextSteps = [];
|
||||
foreach (array_reverse($sequence) as $index => $stepKey) {
|
||||
$workflowStep = $this->registry->getStep($stepKey);
|
||||
if (!$workflowStep) {
|
||||
@ -33,12 +34,13 @@ class WorkflowBuilder {
|
||||
$this->uniqueId(),
|
||||
in_array(Trigger::class, (array)class_implements($workflowStep)) ? Step::TYPE_TRIGGER : Step::TYPE_ACTION,
|
||||
$stepKey,
|
||||
$nextStep,
|
||||
$args
|
||||
$args,
|
||||
$nextSteps
|
||||
);
|
||||
$nextStep = $step->getId();
|
||||
$nextSteps = [new NextStep($step->getId())];
|
||||
$steps[] = $step;
|
||||
}
|
||||
$steps[] = new Step('root', 'root', 'core:root', [], $nextSteps);
|
||||
$steps = array_reverse($steps);
|
||||
return new Workflow(
|
||||
$name,
|
||||
|
@ -20,7 +20,7 @@ class WorkflowStorageTest extends \MailPoetTest
|
||||
public function testItLoadsLatestVersion() {
|
||||
$workflow = $this->createEmptyWorkflow();
|
||||
|
||||
$step1 = new Step('id', Step::TYPE_ACTION, 'key');
|
||||
$step1 = new Step('id', Step::TYPE_ACTION, 'key', [], []);
|
||||
$workflow->setSteps(['id' => $step1]);
|
||||
$this->testee->updateWorkflow($workflow);
|
||||
$updatedWorkflow = $this->testee->getWorkflow($workflow->getId());
|
||||
@ -28,7 +28,7 @@ class WorkflowStorageTest extends \MailPoetTest
|
||||
$this->assertTrue($workflow->getVersionId() < $updatedWorkflow->getVersionId());
|
||||
$this->assertEquals(1, count($updatedWorkflow->getSteps()));
|
||||
|
||||
$step2 = new Step('id-2', Step::TYPE_ACTION, 'key');
|
||||
$step2 = new Step('id-2', Step::TYPE_ACTION, 'key', [], []);
|
||||
$workflow->setSteps(['id' => $step1, 'id-2' => $step2]);
|
||||
$this->testee->updateWorkflow($workflow);
|
||||
$latestWorkflow = $this->testee->getWorkflow($workflow->getId());
|
||||
@ -40,7 +40,7 @@ class WorkflowStorageTest extends \MailPoetTest
|
||||
public function testItLoadsCorrectVersion() {
|
||||
$workflow = $this->createEmptyWorkflow();
|
||||
|
||||
$step1 = new Step('id', Step::TYPE_ACTION, 'key');
|
||||
$step1 = new Step('id', Step::TYPE_ACTION, 'key', [], []);
|
||||
$workflow->setSteps(['id' => $step1]);
|
||||
$this->testee->updateWorkflow($workflow);
|
||||
$updatedWorkflow = $this->testee->getWorkflow($workflow->getId());
|
||||
@ -48,7 +48,7 @@ class WorkflowStorageTest extends \MailPoetTest
|
||||
$this->assertTrue($workflow->getVersionId() < $updatedWorkflow->getVersionId());
|
||||
$this->assertEquals(1, count($updatedWorkflow->getSteps()));
|
||||
|
||||
$step2 = new Step('id-2', Step::TYPE_ACTION, 'key');
|
||||
$step2 = new Step('id-2', Step::TYPE_ACTION, 'key', [], []);
|
||||
$workflow->setSteps(['id' => $step1, 'id-2' => $step2]);
|
||||
$this->testee->updateWorkflow($workflow);
|
||||
$correctWorkflow = $this->testee->getWorkflow($workflow->getId(), $updatedWorkflow->getVersionId());
|
||||
@ -60,7 +60,7 @@ class WorkflowStorageTest extends \MailPoetTest
|
||||
public function testItLoadsOnlyActiveWorkflowsByTrigger() {
|
||||
$workflow = $this->createEmptyWorkflow();
|
||||
$subscriberTrigger = $this->diContainer->get(SegmentSubscribedTrigger::class);
|
||||
$trigger = new Step('id', Step::TYPE_TRIGGER, $subscriberTrigger->getKey());
|
||||
$trigger = new Step('id', Step::TYPE_TRIGGER, $subscriberTrigger->getKey(), [], []);
|
||||
$workflow->setSteps(['id' => $trigger]);
|
||||
$workflow->setStatus(Workflow::STATUS_INACTIVE);
|
||||
$this->testee->updateWorkflow($workflow);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace MailPoet\Test\Automation\Integrations\Core\Actions;
|
||||
|
||||
use MailPoet\Automation\Engine\Control\ActionScheduler;
|
||||
use MailPoet\Automation\Engine\Data\NextStep;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\Workflow;
|
||||
use MailPoet\Automation\Engine\Data\WorkflowRun;
|
||||
@ -15,11 +16,16 @@ class DelayActionTest extends \MailPoetTest {
|
||||
* @dataProvider dataForTestItCalculatesDelayTypesCorrectly
|
||||
*/
|
||||
public function testItCalculatesDelayTypesCorrectly(int $delay, string $type, int $expectation) {
|
||||
|
||||
$step = new Step("1", 'core:delay', 'core:delay', 'next-step', [
|
||||
$step = new Step(
|
||||
'1',
|
||||
'core:delay',
|
||||
'core:delay',
|
||||
[
|
||||
'delay' => $delay,
|
||||
'delay_type' => $type,
|
||||
]);
|
||||
],
|
||||
[new NextStep('next-step')]
|
||||
);
|
||||
$workflow = $this->createMock(Workflow::class);
|
||||
$workflowRun = $this->createMock(WorkflowRun::class);
|
||||
$workflowRun->expects($this->atLeastOnce())->method('getId')->willReturn(1);
|
||||
@ -81,10 +87,16 @@ class DelayActionTest extends \MailPoetTest {
|
||||
*/
|
||||
public function testDelayActionInvalidatesOutsideOfBoundaries(int $delay, bool $expectation) {
|
||||
|
||||
$step = new Step("1", 'core:delay', 'core:delay', 'next-step', [
|
||||
$step = new Step(
|
||||
'1',
|
||||
'core:delay',
|
||||
'core:delay',
|
||||
[
|
||||
'delay' => $delay,
|
||||
'delay_type' => "HOURS",
|
||||
]);
|
||||
],
|
||||
[new NextStep('next-step')]
|
||||
);
|
||||
$workflow = $this->createMock(Workflow::class);
|
||||
$actionScheduler = $this->createMock(ActionScheduler::class);
|
||||
$testee = new DelayAction($actionScheduler);
|
||||
|
@ -66,7 +66,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$this->segmentSubject = $this->diContainer->get(SegmentSubject::class);
|
||||
|
||||
$this->email = (new Newsletter())->withAutomationType()->create();
|
||||
$this->step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $this->email->getId()]);
|
||||
$this->step = new Step('step-id', Step::TYPE_ACTION, 'step-key',['email_id' => $this->email->getId()], []);
|
||||
$this->workflow = new Workflow('test-workflow', [], new \WP_User());
|
||||
}
|
||||
|
||||
@ -84,13 +84,13 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
}
|
||||
|
||||
public function testItIsNotValidIfStepHasNoEmail(): void {
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, []);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', [], []);
|
||||
expect($this->action->isValid($this->getSubjects(), $step, $this->workflow))->false();
|
||||
}
|
||||
|
||||
public function testItRequiresAutomationEmailType(): void {
|
||||
$newsletter = (new Newsletter())->withPostNotificationsType()->create();
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $newsletter->getId()]);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', ['email_id' => $newsletter->getId()], []);
|
||||
expect($this->action->isValid($this->getSubjects(), $step, $this->workflow))->false();
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$subjects = $this->getLoadedSubjects($subscriber, $segment);
|
||||
$email = (new Newsletter())->withAutomationType()->create();
|
||||
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $email->getId()]);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', ['email_id' => $email->getId()], []);
|
||||
$workflow = new Workflow('some-workflow', [$step], new \WP_User());
|
||||
$run = new WorkflowRun(1, 1, 'trigger-key', $subjects);
|
||||
|
||||
@ -125,7 +125,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$subjects = $this->getLoadedSubjects($subscriber, $segment);
|
||||
$email = (new Newsletter())->withAutomationType()->create();
|
||||
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $email->getId()]);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', ['email_id' => $email->getId()], []);
|
||||
$workflow = new Workflow('some-workflow', [$step], new \WP_User());
|
||||
$run = new WorkflowRun(1, 1, 'trigger-key', $subjects);
|
||||
|
||||
@ -157,7 +157,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$subjects = $this->getLoadedSubjects($subscriber, $segment);
|
||||
$email = (new Newsletter())->withAutomationType()->create();
|
||||
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $email->getId()]);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', ['email_id' => $email->getId()], []);
|
||||
$workflow = new Workflow('some-workflow', [$step], new \WP_User());
|
||||
$run = new WorkflowRun(1, 1, 'trigger-key', $subjects);
|
||||
|
||||
@ -186,7 +186,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$subjects = $this->getLoadedSubjects($subscriber, $segment);
|
||||
$email = (new Newsletter())->withAutomationType()->create();
|
||||
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $email->getId()]);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', ['email_id' => $email->getId()], []);
|
||||
$workflow = new Workflow('some-workflow', [$step], new \WP_User());
|
||||
$run = new WorkflowRun(1, 1, 'trigger-key', $subjects);
|
||||
|
||||
@ -224,7 +224,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$subjects = $this->getLoadedSubjects($subscriber, $segment);
|
||||
$email = (new Newsletter())->withAutomationType()->create();
|
||||
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $email->getId()]);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', ['email_id' => $email->getId()], []);
|
||||
$workflow = new Workflow('some-workflow', [$step], new \WP_User());
|
||||
$run = new WorkflowRun(1, 1, 'trigger-key', $subjects);
|
||||
|
||||
@ -253,7 +253,7 @@ class SendEmailActionTest extends \MailPoetTest {
|
||||
$subjects = $this->getLoadedSubjects($subscriber, $segment);
|
||||
$email = (new Newsletter())->withAutomationType()->create();
|
||||
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', null, ['email_id' => $email->getId()]);
|
||||
$step = new Step('step-id', Step::TYPE_ACTION, 'step-key', ['email_id' => $email->getId()], []);
|
||||
$workflow = new Workflow('some-workflow', [$step], new \WP_User());
|
||||
$run = new WorkflowRun(1, 1, 'trigger-key', $subjects);
|
||||
|
||||
|
Reference in New Issue
Block a user