From bb4a7e878ec15bc89581ab68b579c9bbf96d0b7d Mon Sep 17 00:00:00 2001 From: James <78121730+itsnewtjam@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:55:51 -0400 Subject: [PATCH 1/4] support multiple components on create incident --- src/Actions/Incident/CreateIncident.php | 36 +++++++++++++++++-- .../Incident/CreateIncidentRequestData.php | 13 +++---- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/Actions/Incident/CreateIncident.php b/src/Actions/Incident/CreateIncident.php index 464b5911..9c9be99d 100644 --- a/src/Actions/Incident/CreateIncident.php +++ b/src/Actions/Incident/CreateIncident.php @@ -3,6 +3,7 @@ namespace Cachet\Actions\Incident; use Cachet\Data\Requests\Incident\CreateIncidentRequestData; +use Cachet\Enums\ComponentStatusEnum; use Cachet\Models\Component; use Cachet\Models\Incident; use Cachet\Models\IncidentTemplate; @@ -26,10 +27,16 @@ public function handle(CreateIncidentRequestData $data): Incident // @todo Dispatch notification that incident was created. - return Incident::create(array_merge( + $incident = Incident::create(array_merge( ['guid' => Str::uuid()], $data->toArray() )); + + if (!empty($data->components)) { + $this->associateComponents($incident, $data->components); + } + + return $incident; } /** @@ -37,6 +44,8 @@ public function handle(CreateIncidentRequestData $data): Incident */ private function parseTemplate(IncidentTemplate $template, CreateIncidentRequestData $data): string { + $firstComponent = !empty($data->components) ? $data->components[0] : null; + $vars = array_merge($data->templateVars, [ 'incident' => [ 'name' => $data->name, @@ -46,11 +55,32 @@ private function parseTemplate(IncidentTemplate $template, CreateIncidentRequest 'notify' => $data->notifications ?? false, 'stickied' => $data->stickied ?? false, 'occurred_at' => $data->occurredAt ?? Carbon::now(), - 'component' => $data->componentId ? Component::find($data->componentId) : null, - 'component_status' => $data->componentStatus ?? null, + 'component' => $firstComponent ? Component::find($data->componentId) : null, + 'component_status' => $firstComponent ? ComponentStatusEnum::from($firstComponent['component_status']) : null, + 'components' => collect($data->components)->map(function ($comp) { + return [ + 'component' => Component::find($comp['component_id']), + 'status' => ComponentStatusEnum::from($comp['component_status']), + ]; + })->toArray(), ], ]); return $template->render($vars); } + + /** + * Associate components with the incident. + */ + private function associateComponents(Incident $incident, array $components): void + { + foreach ($components as $componentData) { + $componentId = $componentData['component_id']; + $componentStatus = ComponentStatusEnum::from($componentData['component_status']); + + $incident->components()->attach($componentId, ['component_status' => $componentStatus->value]); + + Component::query()->find($componentId)?->update(['status' => $componentStatus->value]); + } + } } diff --git a/src/Data/Requests/Incident/CreateIncidentRequestData.php b/src/Data/Requests/Incident/CreateIncidentRequestData.php index 38df9993..777647a8 100644 --- a/src/Data/Requests/Incident/CreateIncidentRequestData.php +++ b/src/Data/Requests/Incident/CreateIncidentRequestData.php @@ -29,10 +29,7 @@ public function __construct( public readonly bool $notifications = false, public readonly ?string $occurredAt = null, public readonly array $templateVars = [], - #[Exists(Component::class, 'id')] - public readonly ?int $componentId = null, - #[Enum(ComponentStatusEnum::class)] - public readonly ?ComponentStatusEnum $componentStatus = null, + public readonly array $components = [], ) {} public static function rules(ValidationContext $context): array @@ -47,8 +44,9 @@ public static function rules(ValidationContext $context): array 'notifications' => ['boolean'], 'occurred_at' => ['nullable', 'string'], 'template_vars' => ['array'], - 'component_id' => [Rule::exists('components', 'id')], - 'component_status' => ['nullable', Rule::enum(ComponentStatusEnum::class), 'required_with:component_id'], + 'components' => ['array'], + 'components.*.component_id' => [Rule::exists('components', 'id')], + 'components.*.component_status' => ['nullable', Rule::enum(ComponentStatusEnum::class), 'required_with:component_id'], ]; } @@ -64,8 +62,7 @@ public function withMessage(string $message): self notifications: $this->notifications, occurredAt: $this->occurredAt, templateVars: $this->templateVars, - componentId: $this->componentId, - componentStatus: $this->componentStatus, + components: $this->components, ); } } From 08449fb66129d482b3e18d8d686026762fa56dd2 Mon Sep 17 00:00:00 2001 From: James <78121730+itsnewtjam@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:08:16 -0400 Subject: [PATCH 2/4] detach incident_components when incident deleted --- src/Models/Incident.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Models/Incident.php b/src/Models/Incident.php index 4e624807..3f255304 100644 --- a/src/Models/Incident.php +++ b/src/Models/Incident.php @@ -104,6 +104,10 @@ protected static function boot() self::creating(function (Incident $model) { $model->guid = Str::uuid(); }); + + static::deleting(function ($incident) { + $incident->components()->detach(); + }); } /** From 18e01cf83ce95f63fb4026325be617bfa3ed0f84 Mon Sep 17 00:00:00 2001 From: James <78121730+itsnewtjam@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:34:59 -0400 Subject: [PATCH 3/4] fix reference to old componentId, remove unused imports --- src/Actions/Incident/CreateIncident.php | 2 +- src/Data/Requests/Incident/CreateIncidentRequestData.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Actions/Incident/CreateIncident.php b/src/Actions/Incident/CreateIncident.php index 9c9be99d..92bf2548 100644 --- a/src/Actions/Incident/CreateIncident.php +++ b/src/Actions/Incident/CreateIncident.php @@ -55,7 +55,7 @@ private function parseTemplate(IncidentTemplate $template, CreateIncidentRequest 'notify' => $data->notifications ?? false, 'stickied' => $data->stickied ?? false, 'occurred_at' => $data->occurredAt ?? Carbon::now(), - 'component' => $firstComponent ? Component::find($data->componentId) : null, + 'component' => $firstComponent ? Component::find($firstComponent['component_id']) : null, 'component_status' => $firstComponent ? ComponentStatusEnum::from($firstComponent['component_status']) : null, 'components' => collect($data->components)->map(function ($comp) { return [ diff --git a/src/Data/Requests/Incident/CreateIncidentRequestData.php b/src/Data/Requests/Incident/CreateIncidentRequestData.php index 777647a8..e818c297 100644 --- a/src/Data/Requests/Incident/CreateIncidentRequestData.php +++ b/src/Data/Requests/Incident/CreateIncidentRequestData.php @@ -5,10 +5,8 @@ use Cachet\Data\BaseData; use Cachet\Enums\ComponentStatusEnum; use Cachet\Enums\IncidentStatusEnum; -use Cachet\Models\Component; use Illuminate\Validation\Rule; use Spatie\LaravelData\Attributes\Validation\Enum; -use Spatie\LaravelData\Attributes\Validation\Exists; use Spatie\LaravelData\Attributes\Validation\Max; use Spatie\LaravelData\Attributes\Validation\RequiredWithout; use Spatie\LaravelData\Support\Validation\ValidationContext; From 2e197530a76860baa8e31e2d98ef26539cd46452 Mon Sep 17 00:00:00 2001 From: James <78121730+itsnewtjam@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:26:28 -0400 Subject: [PATCH 4/4] create test for api create incident with components --- src/Http/Resources/Incident.php | 1 - tests/Feature/Api/IncidentTest.php | 32 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Http/Resources/Incident.php b/src/Http/Resources/Incident.php index 777f1bda..9d67c1ce 100644 --- a/src/Http/Resources/Incident.php +++ b/src/Http/Resources/Incident.php @@ -17,7 +17,6 @@ public function toAttributes(Request $request): array 'guid' => $this->guid, 'name' => $this->name, 'message' => $this->message, - 'component_id' => $this->component_id, 'visible' => $this->visible, 'stickied' => $this->stickied, 'notifications' => $this->notifications, diff --git a/tests/Feature/Api/IncidentTest.php b/tests/Feature/Api/IncidentTest.php index 9ddf7926..4d1efe7a 100644 --- a/tests/Feature/Api/IncidentTest.php +++ b/tests/Feature/Api/IncidentTest.php @@ -1,6 +1,7 @@ create(), ['incidents.manage']); + + $component1 = Component::factory()->create(); + $component2 = Component::factory()->create(); + + $response = postJson('/status/api/incidents?include=components', [ + 'name' => 'New Incident Occurred', + 'message' => 'Something went wrong.', + 'status' => 2, + 'components' => [ + ['component_id' => $component1->id, 'component_status' => 2], + ['component_id' => $component2->id, 'component_status' => 3], + ], + ]); + + $response->assertCreated(); + $response->assertJson([ + 'data' => [ + 'relationships' => [ + 'components' => [ + 'data' => [ + ['type' => 'components', 'id' => $component1->id], + ['type' => 'components', 'id' => $component2->id], + ] + ] + ], + ], + ]); +}); + it('cannot create an incident with bad data', function (array $payload) { Sanctum::actingAs(User::factory()->create(), ['incidents.manage']);