diff --git a/mailpoet/lib/Automation/Engine/Endpoints/Workflows/WorkflowsDeleteEndpoint.php b/mailpoet/lib/Automation/Engine/Endpoints/Workflows/WorkflowsDeleteEndpoint.php new file mode 100644 index 0000000000..4fc42a52c2 --- /dev/null +++ b/mailpoet/lib/Automation/Engine/Endpoints/Workflows/WorkflowsDeleteEndpoint.php @@ -0,0 +1,42 @@ +workflowStorage = $workflowStorage; + } + + public function handle(Request $request): Response { + $workflowId = $request->getParam('id'); + if (!is_int($workflowId)) { + throw InvalidStateException::create(); + } + $existingWorkflow = $this->workflowStorage->getWorkflow($workflowId); + if (!$existingWorkflow instanceof Workflow) { + throw InvalidStateException::create(); + } + $this->workflowStorage->deleteWorkflow($existingWorkflow); + + return new Response(null); + } + + public static function getRequestSchema(): array { + return [ + 'id' => Builder::integer()->required(), + ]; + } +} diff --git a/mailpoet/lib/Automation/Engine/Engine.php b/mailpoet/lib/Automation/Engine/Engine.php index 689db83d21..f5bbc1d6d5 100644 --- a/mailpoet/lib/Automation/Engine/Engine.php +++ b/mailpoet/lib/Automation/Engine/Engine.php @@ -8,6 +8,7 @@ use MailPoet\Automation\Engine\Control\TriggerHandler; use MailPoet\Automation\Engine\Endpoints\System\DatabaseDeleteEndpoint; use MailPoet\Automation\Engine\Endpoints\System\DatabasePostEndpoint; use MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsCreateFromTemplateEndpoint; +use MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsDeleteEndpoint; use MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsDuplicateEndpoint; use MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsGetEndpoint; use MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsPutEndpoint; @@ -73,8 +74,9 @@ class Engine { $this->wordPress->addAction(Hooks::API_INITIALIZE, function (API $api) { $api->registerGetRoute('workflows', WorkflowsGetEndpoint::class); $api->registerPutRoute('workflows/(?P\d+)', WorkflowsPutEndpoint::class); - $api->registerPostRoute('workflows/create-from-template', WorkflowsCreateFromTemplateEndpoint::class); + $api->registerDeleteRoute('workflows/(?P\d+)', WorkflowsDeleteEndpoint::class); $api->registerPostRoute('workflows/(?P\d+)/duplicate', WorkflowsDuplicateEndpoint::class); + $api->registerPostRoute('workflows/create-from-template', WorkflowsCreateFromTemplateEndpoint::class); $api->registerPostRoute('system/database', DatabasePostEndpoint::class); $api->registerDeleteRoute('system/database', DatabaseDeleteEndpoint::class); $api->registerGetRoute('workflow-templates', WorkflowTemplatesGetEndpoint::class); diff --git a/mailpoet/lib/Automation/Engine/Migrations/Migrator.php b/mailpoet/lib/Automation/Engine/Migrations/Migrator.php index d8347fde32..d713d94e29 100644 --- a/mailpoet/lib/Automation/Engine/Migrations/Migrator.php +++ b/mailpoet/lib/Automation/Engine/Migrations/Migrator.php @@ -59,6 +59,7 @@ class Migrator { updated_at timestamp NOT NULL, subjects longtext, PRIMARY KEY (id), + INDEX (workflow_id), INDEX (status) ); "); diff --git a/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php b/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php index 334f004e1d..242d6c8075 100644 --- a/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php +++ b/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php @@ -142,6 +142,44 @@ class WorkflowStorage { }, (array)$data); } + public function deleteWorkflow(Workflow $workflow): void { + $workflowTable = esc_sql($this->workflowTable); + $versionTable = esc_sql($this->versionsTable); + $workflowRunTable = esc_sql($this->wpdb->prefix . 'mailpoet_workflow_runs'); + $workflowRunLogTable = esc_sql($this->wpdb->prefix . 'mailpoet_workflow_run_logs'); + $workflowId = $workflow->getId(); + $runLogsQuery = $this->wpdb->prepare( + " + DELETE FROM $workflowRunLogTable + WHERE workflow_run_id IN ( + SELECT id FROM $workflowRunTable + WHERE workflow_id = %d + ) + ", + $workflowId + ); + + if (!is_string($runLogsQuery)) { + throw Exceptions\InvalidStateException::create(); + } + $logsDeleted = $this->wpdb->query($runLogsQuery); + if (!is_int($logsDeleted)) { + throw Exceptions::databaseError($this->wpdb->last_error); + } + $runsDeleted = $this->wpdb->delete($this->wpdb->prefix . 'mailpoet_workflow_runs', ['workflow_id' => $workflowId]); + if (!is_int($runsDeleted)) { + throw Exceptions::databaseError($this->wpdb->last_error); + } + $versionsDeleted = $this->wpdb->delete($versionTable, ['workflow_id' => $workflowId]); + if (!is_int($versionsDeleted)) { + throw Exceptions::databaseError($this->wpdb->last_error); + } + $workflowDeleted = $this->wpdb->delete($workflowTable, ['id' => $workflowId]); + if (!is_int($workflowDeleted)) { + throw Exceptions::databaseError($this->wpdb->last_error); + } + } + public function truncate(): bool { $workflowTable = esc_sql($this->workflowTable); $versionTable = esc_sql($this->versionsTable); diff --git a/mailpoet/lib/DI/ContainerConfigurator.php b/mailpoet/lib/DI/ContainerConfigurator.php index 6372f92f7f..319f694a74 100644 --- a/mailpoet/lib/DI/ContainerConfigurator.php +++ b/mailpoet/lib/DI/ContainerConfigurator.php @@ -143,6 +143,7 @@ class ContainerConfigurator implements IContainerConfigurator { $container->autowire(\MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsPutEndpoint::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsCreateFromTemplateEndpoint::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsDuplicateEndpoint::class)->setPublic(true); + $container->autowire(\MailPoet\Automation\Engine\Endpoints\Workflows\WorkflowsDeleteEndpoint::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Endpoints\System\DatabasePostEndpoint::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Endpoints\System\DatabaseDeleteEndpoint::class)->setPublic(true); // Automation - core integration diff --git a/mailpoet/tests/integration/Automation/Engine/Storage/WorkflowStorageTest.php b/mailpoet/tests/integration/Automation/Engine/Storage/WorkflowStorageTest.php index 50dc74a6d3..bbd38955ee 100644 --- a/mailpoet/tests/integration/Automation/Engine/Storage/WorkflowStorageTest.php +++ b/mailpoet/tests/integration/Automation/Engine/Storage/WorkflowStorageTest.php @@ -4,6 +4,10 @@ namespace MailPoet\Test\Automation\Engine\Storage; use MailPoet\Automation\Engine\Data\Step; use MailPoet\Automation\Engine\Data\Workflow; +use MailPoet\Automation\Engine\Data\WorkflowRun; +use MailPoet\Automation\Engine\Data\WorkflowRunLog; +use MailPoet\Automation\Engine\Storage\WorkflowRunLogStorage; +use MailPoet\Automation\Engine\Storage\WorkflowRunStorage; use MailPoet\Automation\Engine\Storage\WorkflowStorage; use MailPoet\Automation\Integrations\MailPoet\Triggers\SomeoneSubscribesTrigger; @@ -73,6 +77,60 @@ class WorkflowStorageTest extends \MailPoetTest $this->assertEmpty($this->testee->getActiveWorkflowsByTrigger($subscriberTrigger)); } + public function testItCanDeleteAWorkflow() { + $workflowToDelete = $this->createEmptyWorkflow(); + $workflowToKeep = $this->createEmptyWorkflow(); + expect($this->testee->getWorkflows())->count(2); + $this->testee->deleteWorkflow($workflowToDelete); + expect($this->testee->getWorkflows())->count(1); + expect($this->testee->getWorkflow($workflowToDelete->getId()))->null(); + $workflowToKeepFromDatabase = $this->testee->getWorkflow($workflowToKeep->getId()); + $this->assertInstanceOf(Workflow::class, $workflowToKeepFromDatabase); + expect($workflowToKeepFromDatabase->getVersionId())->notNull(); + } + + public function testItCanDeleteWorkflowsRelatedData() { + $workflowRunStorage = $this->diContainer->get(WorkflowRunStorage::class); + $workflowRunLogStorage = $this->diContainer->get(WorkflowRunLogStorage::class); + $workflows = [ + 'toDelete' => $this->createEmptyWorkflow(), + 'toKeep' => $this->createEmptyWorkflow() + ]; + $runs = [ + 'toDelete' => [], + 'toKeep' => [] + ]; + $runLogs = [ + 'toDelete' => [], + 'toKeep' => [] + ]; + foreach ($workflows as $type => $workflow) { + for ($i = 0; $i < 2; $i++) { + $workflowRun = new WorkflowRun($workflow->getId(), $workflow->getVersionId(), 'trigger-key', []); + $runId = $workflowRunStorage->createWorkflowRun($workflowRun); + $runs[$type][] = $runId; + for ($i = 0; $i < 2; $i++) { + $log = new WorkflowRunLog($runId, "step-{$i}"); + $logId = $workflowRunLogStorage->createWorkflowRunLog($log); + $runLogs[$type][] = $logId; + } + } + } + $this->testee->deleteWorkflow($workflows['toDelete']); + foreach ($runs['toDelete'] as $runId) { + expect($workflowRunStorage->getWorkflowRun($runId))->null(); + } + foreach ($runs['toKeep'] as $runId) { + expect($workflowRunStorage->getWorkflowRun($runId))->notNull(); + } + foreach ($runLogs['toDelete'] as $runLogId) { + expect($workflowRunLogStorage->getWorkflowRunLog($runLogId))->null(); + } + foreach ($runLogs['toKeep'] as $runLogId) { + expect($workflowRunLogStorage->getWorkflowRunLog($runLogId))->notNull(); + } + } + private function createEmptyWorkflow(string $name="test"): Workflow { $workflow = new Workflow($name, [], new \WP_User()); $workflowId = $this->testee->createWorkflow($workflow);