diff --git a/app/Jobs/Automation/DispatchPostTriggerAutomationsJob.php b/app/Jobs/Automation/DispatchPostTriggerAutomationsJob.php new file mode 100644 index 00000000..7982630d --- /dev/null +++ b/app/Jobs/Automation/DispatchPostTriggerAutomationsJob.php @@ -0,0 +1,33 @@ +onQueue('automations'); + } + + public function handle(DispatchPostTriggerAutomations $dispatch): void + { + $dispatch($this->post, $this->triggerType); + } +} diff --git a/app/Observers/PostObserver.php b/app/Observers/PostObserver.php index f34b49a7..33456b27 100644 --- a/app/Observers/PostObserver.php +++ b/app/Observers/PostObserver.php @@ -4,31 +4,29 @@ namespace App\Observers; -use App\Actions\Automation\Trigger\DispatchPostTriggerAutomations; use App\Enums\Automation\Trigger\Type as TriggerType; use App\Enums\Post\Status as PostStatus; +use App\Jobs\Automation\DispatchPostTriggerAutomationsJob; use App\Models\Post; class PostObserver { - public function __construct(private DispatchPostTriggerAutomations $dispatch) {} - public function saved(Post $post): void { if (! $post->wasChanged('status')) { return; } - $status = $post->status; - - if ($status === PostStatus::Published) { - ($this->dispatch)($post, TriggerType::PostPublished); + $triggerType = match ($post->status) { + PostStatus::Published => TriggerType::PostPublished, + PostStatus::Scheduled => TriggerType::PostScheduled, + default => null, + }; + if ($triggerType === null) { return; } - if ($status === PostStatus::Scheduled) { - ($this->dispatch)($post, TriggerType::PostScheduled); - } + DispatchPostTriggerAutomationsJob::dispatch($post, $triggerType)->afterCommit(); } } diff --git a/resources/js/components/posts/editor/FacebookSettings.vue b/resources/js/components/posts/editor/FacebookSettings.vue index 1f7352bd..af379ebb 100644 --- a/resources/js/components/posts/editor/FacebookSettings.vue +++ b/resources/js/components/posts/editor/FacebookSettings.vue @@ -5,7 +5,7 @@ import { computed, ref } from 'vue'; import { Avatar } from '@/components/ui/avatar'; import { getMediaValidationWarning } from '@/composables/useMedia'; import { getPlatformLogo } from '@/composables/usePlatformLogo'; -import { ContentType } from '@/enums/content-type'; +import { ContentType } from '@/types/content-type'; import type { MediaItem } from '@/types/media'; interface SocialAccount { diff --git a/tests/Feature/Automation/Trigger/PostTriggersTest.php b/tests/Feature/Automation/Trigger/PostTriggersTest.php index 9d0b2191..4a8079e3 100644 --- a/tests/Feature/Automation/Trigger/PostTriggersTest.php +++ b/tests/Feature/Automation/Trigger/PostTriggersTest.php @@ -2,7 +2,10 @@ declare(strict_types=1); +use App\Actions\Automation\Trigger\DispatchPostTriggerAutomations; +use App\Enums\Automation\Trigger\Type as TriggerType; use App\Enums\Post\Status as PostStatus; +use App\Jobs\Automation\DispatchPostTriggerAutomationsJob; use App\Jobs\Automation\ProcessAutomationNode; use App\Models\Automation; use App\Models\AutomationRun; @@ -12,7 +15,42 @@ beforeEach(fn () => Bus::fake()); -it('dispatches a run when a post becomes published in the workspace', function () { +it('dispatches the trigger job when a post becomes published', function () { + $post = Post::factory()->create(['status' => PostStatus::Draft]); + $post->update(['status' => PostStatus::Published]); + + Bus::assertDispatched( + DispatchPostTriggerAutomationsJob::class, + fn (DispatchPostTriggerAutomationsJob $job) => $job->post->is($post) + && $job->triggerType === TriggerType::PostPublished, + ); +}); + +it('dispatches the trigger job when a post becomes scheduled', function () { + $post = Post::factory()->create(['status' => PostStatus::Draft]); + $post->update(['status' => PostStatus::Scheduled]); + + Bus::assertDispatched( + DispatchPostTriggerAutomationsJob::class, + fn (DispatchPostTriggerAutomationsJob $job) => $job->triggerType === TriggerType::PostScheduled, + ); +}); + +it('does not dispatch for status changes other than published or scheduled', function () { + $post = Post::factory()->create(['status' => PostStatus::Draft]); + $post->update(['status' => PostStatus::Publishing]); + + Bus::assertNotDispatched(DispatchPostTriggerAutomationsJob::class); +}); + +it('does not dispatch when the status did not change', function () { + $post = Post::factory()->create(['status' => PostStatus::Draft]); + $post->update(['content' => 'edited body']); + + Bus::assertNotDispatched(DispatchPostTriggerAutomationsJob::class); +}); + +it('creates a run and advances to the next node for a matching active automation', function () { $workspace = Workspace::factory()->create(); $automation = Automation::factory()->active()->for($workspace)->create([ 'nodes' => [ @@ -22,8 +60,9 @@ 'connections' => [['id' => 'e1', 'source' => 'trigger_1', 'target' => 'end_1']], ]); - $post = Post::factory()->for($workspace)->create(['status' => PostStatus::Draft]); - $post->update(['status' => PostStatus::Published]); + $post = Post::factory()->for($workspace)->create(['status' => PostStatus::Published]); + + app(DispatchPostTriggerAutomations::class)($post, TriggerType::PostPublished); $runs = AutomationRun::where('automation_id', $automation->id)->get(); expect($runs)->toHaveCount(1); @@ -33,23 +72,7 @@ Bus::assertDispatched(ProcessAutomationNode::class); }); -it('dispatches when a post becomes scheduled', function () { - $workspace = Workspace::factory()->create(); - $automation = Automation::factory()->active()->for($workspace)->create([ - 'nodes' => [ - ['id' => 'trigger_1', 'type' => 'trigger', 'position' => ['x' => 0, 'y' => 0], 'data' => ['trigger_type' => 'post_scheduled']], - ['id' => 'end_1', 'type' => 'end', 'position' => ['x' => 200, 'y' => 0], 'data' => []], - ], - 'connections' => [['id' => 'e1', 'source' => 'trigger_1', 'target' => 'end_1']], - ]); - - $post = Post::factory()->for($workspace)->create(['status' => PostStatus::Draft]); - $post->update(['status' => PostStatus::Scheduled]); - - expect(AutomationRun::where('automation_id', $automation->id)->count())->toBe(1); -}); - -it('does not dispatch for automations in a different workspace', function () { +it('does not create a run for automations in a different workspace', function () { $workspaceA = Workspace::factory()->create(); $workspaceB = Workspace::factory()->create(); @@ -61,28 +84,13 @@ 'connections' => [['id' => 'e1', 'source' => 'trigger_1', 'target' => 'end_1']], ]); - $post = Post::factory()->for($workspaceB)->create(['status' => PostStatus::Draft]); - $post->update(['status' => PostStatus::Published]); + $post = Post::factory()->for($workspaceB)->create(['status' => PostStatus::Published]); + + app(DispatchPostTriggerAutomations::class)($post, TriggerType::PostPublished); expect(AutomationRun::where('automation_id', $automation->id)->count())->toBe(0); }); -it('does not dispatch when status change is not to Published or Scheduled', function () { - $workspace = Workspace::factory()->create(); - Automation::factory()->active()->for($workspace)->create([ - 'nodes' => [ - ['id' => 'trigger_1', 'type' => 'trigger', 'position' => ['x' => 0, 'y' => 0], 'data' => ['trigger_type' => 'post_published']], - ['id' => 'end_1', 'type' => 'end', 'position' => ['x' => 200, 'y' => 0], 'data' => []], - ], - 'connections' => [['id' => 'e1', 'source' => 'trigger_1', 'target' => 'end_1']], - ]); - - $post = Post::factory()->for($workspace)->create(['status' => PostStatus::Draft]); - $post->update(['status' => PostStatus::Publishing]); - - expect(AutomationRun::count())->toBe(0); -}); - it('skips paused automations', function () { $workspace = Workspace::factory()->create(); Automation::factory()->paused()->for($workspace)->create([ @@ -93,8 +101,9 @@ 'connections' => [['id' => 'e1', 'source' => 'trigger_1', 'target' => 'end_1']], ]); - $post = Post::factory()->for($workspace)->create(['status' => PostStatus::Draft]); - $post->update(['status' => PostStatus::Published]); + $post = Post::factory()->for($workspace)->create(['status' => PostStatus::Published]); + + app(DispatchPostTriggerAutomations::class)($post, TriggerType::PostPublished); expect(AutomationRun::count())->toBe(0); });