Move automation reads and delete into actions
This commit is contained in:
parent
ab7c7c8bdf
commit
af4d83190e
9 changed files with 230 additions and 53 deletions
|
|
@ -11,12 +11,12 @@
|
|||
|
||||
class CreateAutomation
|
||||
{
|
||||
public function __invoke(Workspace $workspace, User $user, string $name): Automation
|
||||
public function __invoke(Workspace $workspace, User $user, ?string $name = null): Automation
|
||||
{
|
||||
return Automation::create([
|
||||
'workspace_id' => $workspace->id,
|
||||
'user_id' => $user->id,
|
||||
'name' => $name,
|
||||
'name' => $name ?: __('automations.default_name'),
|
||||
'status' => Status::Draft,
|
||||
'nodes' => [],
|
||||
'connections' => [],
|
||||
|
|
|
|||
15
app/Actions/Automation/Automation/DeleteAutomation.php
Normal file
15
app/Actions/Automation/Automation/DeleteAutomation.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Automation\Automation;
|
||||
|
||||
use App\Models\Automation;
|
||||
|
||||
class DeleteAutomation
|
||||
{
|
||||
public function __invoke(Automation $automation): void
|
||||
{
|
||||
$automation->delete();
|
||||
}
|
||||
}
|
||||
27
app/Actions/Automation/Automation/GetAutomationDetails.php
Normal file
27
app/Actions/Automation/Automation/GetAutomationDetails.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Automation\Automation;
|
||||
|
||||
use App\Models\Automation;
|
||||
use App\Models\AutomationRun;
|
||||
use App\Models\AutomationTriggerItem;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class GetAutomationDetails
|
||||
{
|
||||
/**
|
||||
* @return array{
|
||||
* runs: Collection<int, AutomationRun>,
|
||||
* triggerItems: Collection<int, AutomationTriggerItem>,
|
||||
* }
|
||||
*/
|
||||
public function __invoke(Automation $automation): array
|
||||
{
|
||||
return [
|
||||
'runs' => $automation->runs()->excludingDryRuns()->latest()->take(50)->get(),
|
||||
'triggerItems' => $automation->triggerItems()->with('run')->latest()->take(50)->get(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Automation\Automation;
|
||||
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Models\Automation;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Services\Social\PinterestPublisher;
|
||||
use App\Services\Social\TikTokCreatorInfo;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Collection as SupportCollection;
|
||||
|
||||
class GetAutomationEditorData
|
||||
{
|
||||
public function __construct(
|
||||
private PinterestPublisher $pinterestPublisher,
|
||||
private TikTokCreatorInfo $tikTokCreatorInfo,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* socialAccounts: Collection<int, SocialAccount>,
|
||||
* pinterestBoards: SupportCollection<string, array<int, mixed>>,
|
||||
* tiktokCreatorInfos: SupportCollection<string, mixed>,
|
||||
* }
|
||||
*/
|
||||
public function __invoke(Automation $automation): array
|
||||
{
|
||||
$socialAccounts = $automation->workspace->socialAccounts()->active()->get();
|
||||
|
||||
$pinterestBoards = $socialAccounts
|
||||
->where('platform', Platform::Pinterest)
|
||||
->mapWithKeys(fn ($account) => [
|
||||
$account->id => rescue(
|
||||
fn () => $this->pinterestPublisher->getBoards($account),
|
||||
[],
|
||||
report: false,
|
||||
),
|
||||
]);
|
||||
|
||||
$tiktokCreatorInfos = $socialAccounts
|
||||
->where('platform', Platform::TikTok)
|
||||
->mapWithKeys(fn ($account) => [
|
||||
$account->id => rescue(
|
||||
fn () => $this->tikTokCreatorInfo->fetch($account),
|
||||
null,
|
||||
report: false,
|
||||
),
|
||||
])
|
||||
->filter();
|
||||
|
||||
return [
|
||||
'socialAccounts' => $socialAccounts,
|
||||
'pinterestBoards' => $pinterestBoards,
|
||||
'tiktokCreatorInfos' => $tiktokCreatorInfos,
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Actions/Automation/Automation/ListAutomations.php
Normal file
20
app/Actions/Automation/Automation/ListAutomations.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Automation\Automation;
|
||||
|
||||
use App\Models\Automation;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
class ListAutomations
|
||||
{
|
||||
public function __invoke(Workspace $workspace, ?int $perPage = null): LengthAwarePaginator
|
||||
{
|
||||
return Automation::query()
|
||||
->where('workspace_id', $workspace->id)
|
||||
->orderByDesc('created_at')
|
||||
->paginate($perPage ?? (int) config('app.pagination.default'));
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,14 @@
|
|||
|
||||
use App\Actions\Automation\Automation\ActivateAutomation;
|
||||
use App\Actions\Automation\Automation\CreateAutomation;
|
||||
use App\Actions\Automation\Automation\DeleteAutomation;
|
||||
use App\Actions\Automation\Automation\GetAutomationDetails;
|
||||
use App\Actions\Automation\Automation\GetAutomationEditorData;
|
||||
use App\Actions\Automation\Automation\ListAutomations;
|
||||
use App\Actions\Automation\Automation\PauseAutomation;
|
||||
use App\Actions\Automation\Automation\UpdateAutomation;
|
||||
use App\Actions\Automation\Run\RetryRunFromNode;
|
||||
use App\Actions\Automation\Run\TestAutomation;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\App\Automations\ActivateAutomationRequest;
|
||||
use App\Http\Requests\App\Automations\PauseAutomationRequest;
|
||||
|
|
@ -26,8 +29,6 @@
|
|||
use App\Http\Resources\AutomationTriggerItemResource;
|
||||
use App\Models\Automation;
|
||||
use App\Models\AutomationRun;
|
||||
use App\Services\Social\PinterestPublisher;
|
||||
use App\Services\Social\TikTokCreatorInfo;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Inertia\Inertia;
|
||||
|
|
@ -35,13 +36,12 @@
|
|||
|
||||
class AutomationController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
public function index(ListAutomations $list): Response
|
||||
{
|
||||
$workspace = request()->user()->currentWorkspace;
|
||||
|
||||
$automations = Inertia::scroll(fn () => AutomationResource::collection(
|
||||
Automation::query()
|
||||
->where('workspace_id', request()->user()->current_workspace_id)
|
||||
->orderByDesc('created_at')
|
||||
->paginate(config('app.pagination.default'))
|
||||
$list($workspace)
|
||||
));
|
||||
|
||||
return Inertia::render('automations/Index', [
|
||||
|
|
@ -51,52 +51,24 @@ public function index(): Response
|
|||
|
||||
public function store(StoreAutomationRequest $request, CreateAutomation $create): RedirectResponse
|
||||
{
|
||||
$name = $request->validated('name');
|
||||
|
||||
if (! $name || $name === 'automations.default_name') {
|
||||
$name = __('automations.default_name');
|
||||
}
|
||||
|
||||
$automation = $create(
|
||||
$request->user()->currentWorkspace,
|
||||
$request->user(),
|
||||
$name,
|
||||
);
|
||||
|
||||
return redirect()->route('app.automations.edit', $automation->id);
|
||||
}
|
||||
|
||||
public function edit(Automation $automation): Response
|
||||
public function edit(Automation $automation, GetAutomationEditorData $editorData): Response
|
||||
{
|
||||
$this->authorize('update', $automation);
|
||||
|
||||
$socialAccounts = $automation->workspace->socialAccounts()->active()->get();
|
||||
['socialAccounts' => $socialAccounts, 'pinterestBoards' => $pinterestBoards, 'tiktokCreatorInfos' => $tiktokCreatorInfos] = $editorData($automation);
|
||||
|
||||
$platformConfigs = $socialAccounts->mapWithKeys(fn ($account) => [
|
||||
$account->id => new PlatformConfigResource($account),
|
||||
]);
|
||||
|
||||
$pinterestBoards = $socialAccounts
|
||||
->where('platform', Platform::Pinterest)
|
||||
->mapWithKeys(fn ($account) => [
|
||||
$account->id => rescue(
|
||||
fn () => app(PinterestPublisher::class)->getBoards($account),
|
||||
[],
|
||||
report: false,
|
||||
),
|
||||
]);
|
||||
|
||||
$tiktokCreatorInfos = $socialAccounts
|
||||
->where('platform', Platform::TikTok)
|
||||
->mapWithKeys(fn ($account) => [
|
||||
$account->id => rescue(
|
||||
fn () => app(TikTokCreatorInfo::class)->fetch($account),
|
||||
null,
|
||||
report: false,
|
||||
),
|
||||
])
|
||||
->filter();
|
||||
|
||||
return Inertia::render('automations/Form', [
|
||||
'automation' => AutomationResource::make($automation),
|
||||
'socialAccounts' => SocialAccountResource::collection($socialAccounts),
|
||||
|
|
@ -106,16 +78,16 @@ public function edit(Automation $automation): Response
|
|||
]);
|
||||
}
|
||||
|
||||
public function show(Automation $automation): Response
|
||||
public function show(Automation $automation, GetAutomationDetails $details): Response
|
||||
{
|
||||
$this->authorize('view', $automation);
|
||||
|
||||
['runs' => $runs, 'triggerItems' => $triggerItems] = $details($automation);
|
||||
|
||||
return Inertia::render('automations/Show', [
|
||||
'automation' => AutomationResource::make($automation),
|
||||
'runs' => AutomationRunResource::collection($automation->runs()->excludingDryRuns()->latest()->take(50)->get()),
|
||||
'triggerItems' => AutomationTriggerItemResource::collection(
|
||||
$automation->triggerItems()->with('run')->latest()->take(50)->get()
|
||||
),
|
||||
'runs' => AutomationRunResource::collection($runs),
|
||||
'triggerItems' => AutomationTriggerItemResource::collection($triggerItems),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -128,10 +100,10 @@ public function update(UpdateAutomationRequest $request, Automation $automation,
|
|||
return back();
|
||||
}
|
||||
|
||||
public function destroy(Automation $automation): RedirectResponse
|
||||
public function destroy(Automation $automation, DeleteAutomation $delete): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $automation);
|
||||
$automation->delete();
|
||||
$delete($automation);
|
||||
|
||||
session()->flash('flash.banner', __('automations.flash.deleted'));
|
||||
session()->flash('flash.bannerStyle', 'success');
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ public function authorize(): bool
|
|||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:120'],
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
83
tests/Feature/Automation/Automation/ReadActionsTest.php
Normal file
83
tests/Feature/Automation/Automation/ReadActionsTest.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Actions\Automation\Automation\DeleteAutomation;
|
||||
use App\Actions\Automation\Automation\GetAutomationDetails;
|
||||
use App\Actions\Automation\Automation\GetAutomationEditorData;
|
||||
use App\Actions\Automation\Automation\ListAutomations;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Models\Automation;
|
||||
use App\Models\AutomationRun;
|
||||
use App\Models\AutomationTriggerItem;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\Workspace;
|
||||
use App\Services\Social\PinterestPublisher;
|
||||
use App\Services\Social\TikTokCreatorInfo;
|
||||
|
||||
it('lists only the workspace automations, newest first', function () {
|
||||
$workspace = Workspace::factory()->create();
|
||||
$other = Workspace::factory()->create();
|
||||
|
||||
$older = Automation::factory()->for($workspace)->create(['created_at' => now()->subDay()]);
|
||||
$newer = Automation::factory()->for($workspace)->create(['created_at' => now()]);
|
||||
Automation::factory()->for($other)->create();
|
||||
|
||||
$result = app(ListAutomations::class)($workspace);
|
||||
|
||||
expect($result->total())->toBe(2);
|
||||
expect($result->items()[0]->id)->toBe($newer->id);
|
||||
expect($result->items()[1]->id)->toBe($older->id);
|
||||
});
|
||||
|
||||
it('deletes the automation', function () {
|
||||
$automation = Automation::factory()->create();
|
||||
|
||||
app(DeleteAutomation::class)($automation);
|
||||
|
||||
expect(Automation::find($automation->id))->toBeNull();
|
||||
});
|
||||
|
||||
it('returns non-dry runs and trigger items, newest first', function () {
|
||||
$automation = Automation::factory()->create();
|
||||
$real = AutomationRun::factory()->for($automation)->create();
|
||||
AutomationRun::factory()->for($automation)->create(['is_dry_run' => true]);
|
||||
$item = AutomationTriggerItem::factory()->for($automation)->create();
|
||||
|
||||
$result = app(GetAutomationDetails::class)($automation);
|
||||
|
||||
expect($result['runs'])->toHaveCount(1);
|
||||
expect($result['runs']->first()->id)->toBe($real->id);
|
||||
expect($result['triggerItems'])->toHaveCount(1);
|
||||
expect($result['triggerItems']->first()->id)->toBe($item->id);
|
||||
});
|
||||
|
||||
it('returns only active social accounts for the automation workspace', function () {
|
||||
$this->mock(PinterestPublisher::class);
|
||||
$this->mock(TikTokCreatorInfo::class);
|
||||
|
||||
$workspace = Workspace::factory()->create();
|
||||
$automation = Automation::factory()->for($workspace)->create();
|
||||
$active = SocialAccount::factory()->for($workspace)->create(['platform' => 'instagram', 'is_active' => true]);
|
||||
SocialAccount::factory()->for($workspace)->create(['platform' => 'instagram', 'is_active' => false]);
|
||||
|
||||
$result = app(GetAutomationEditorData::class)($automation);
|
||||
|
||||
expect($result['socialAccounts'])->toHaveCount(1);
|
||||
expect($result['socialAccounts']->first()->id)->toBe($active->id);
|
||||
expect($result['pinterestBoards'])->toBeEmpty();
|
||||
expect($result['tiktokCreatorInfos'])->toBeEmpty();
|
||||
});
|
||||
|
||||
it('maps pinterest boards for pinterest accounts', function () {
|
||||
$this->mock(PinterestPublisher::class, fn ($mock) => $mock->shouldReceive('getBoards')->andReturn([['id' => 'b1']]));
|
||||
$this->mock(TikTokCreatorInfo::class);
|
||||
|
||||
$workspace = Workspace::factory()->create();
|
||||
$automation = Automation::factory()->for($workspace)->create();
|
||||
$pinterest = SocialAccount::factory()->for($workspace)->create(['platform' => Platform::Pinterest->value, 'is_active' => true]);
|
||||
|
||||
$result = app(GetAutomationEditorData::class)($automation);
|
||||
|
||||
expect($result['pinterestBoards']->get($pinterest->id))->toBe([['id' => 'b1']]);
|
||||
});
|
||||
|
|
@ -16,12 +16,14 @@
|
|||
$this->user->refresh();
|
||||
});
|
||||
|
||||
it('creates an automation via POST', function () {
|
||||
it('creates an automation with the default name (web sends no name)', function () {
|
||||
$response = $this->actingAs($this->user)
|
||||
->post(route('app.automations.store'), ['name' => 'My RSS auto']);
|
||||
->post(route('app.automations.store'));
|
||||
|
||||
$response->assertRedirect();
|
||||
expect(Automation::where('workspace_id', $this->workspace->id)->count())->toBe(1);
|
||||
|
||||
$automation = Automation::where('workspace_id', $this->workspace->id)->sole();
|
||||
expect($automation->name)->toBe(__('automations.default_name'));
|
||||
});
|
||||
|
||||
it('updates nodes and connections via PUT', function () {
|
||||
|
|
|
|||
Loading…
Reference in a new issue