From 458804b9e906809f310d29e1f2c3107058c6f724 Mon Sep 17 00:00:00 2001 From: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> Date: Wed, 24 Dec 2025 07:33:33 -0500 Subject: [PATCH 1/5] mark speedtest as scheduled when started using the API --- app/Http/Controllers/Api/V1/SpeedtestController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/Api/V1/SpeedtestController.php b/app/Http/Controllers/Api/V1/SpeedtestController.php index 19519f61e..d605d945c 100644 --- a/app/Http/Controllers/Api/V1/SpeedtestController.php +++ b/app/Http/Controllers/Api/V1/SpeedtestController.php @@ -37,6 +37,7 @@ public function __invoke(Request $request) } $result = RunSpeedtestAction::run( + scheduled: true, serverId: $request->input('server_id'), dispatchedBy: $request->user()->id, ); From fe9febac176f738173cc73d870b538c61fe6c45b Mon Sep 17 00:00:00 2001 From: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> Date: Wed, 24 Dec 2025 08:23:24 -0500 Subject: [PATCH 2/5] feat: add unscheduled attribute to Result model --- app/Models/Result.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/Models/Result.php b/app/Models/Result.php index bba1a37d9..084c04097 100644 --- a/app/Models/Result.php +++ b/app/Models/Result.php @@ -6,6 +6,7 @@ use App\Enums\ResultStatus; use App\Models\Traits\ResultDataAttributes; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Prunable; @@ -54,4 +55,14 @@ public function dispatchedBy(): BelongsTo { return $this->belongsTo(User::class, 'dispatched_by'); } + + /** + * Determine if the result was unscheduled. + */ + protected function unscheduled(): Attribute + { + return Attribute::make( + get: fn (): bool => ! $this->scheduled, + ); + } } From e1dc159f416c25fa677a26ea57f9184bd65b79a8 Mon Sep 17 00:00:00 2001 From: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> Date: Wed, 24 Dec 2025 08:23:38 -0500 Subject: [PATCH 3/5] started reworking listener logic --- app/Listeners/ProcessCompletedSpeedtest.php | 40 ++++++++------------- app/Listeners/ProcessFailedSpeedtest.php | 22 ++++++------ app/Listeners/ProcessUnhealthySpeedtest.php | 37 ++++++------------- 3 files changed, 37 insertions(+), 62 deletions(-) diff --git a/app/Listeners/ProcessCompletedSpeedtest.php b/app/Listeners/ProcessCompletedSpeedtest.php index 1b72f3053..6255ab416 100644 --- a/app/Listeners/ProcessCompletedSpeedtest.php +++ b/app/Listeners/ProcessCompletedSpeedtest.php @@ -31,11 +31,24 @@ public function handle(SpeedtestCompleted $event): void { $result = $event->result; - $result->loadMissing(['dispatchedBy']); + if ($result->healthy === false) { + return; + } + + // Notify the user who dispatched the speedtest. + if ($result->dispatched_by && $result->unscheduled) { + $result->loadMissing('dispatchedBy'); + + $this->notifyDispatchingUser($result); + } + + // Don't send notifications for unscheduled speedtests. + if ($result->unscheduled) { + return; + } $this->notifyAppriseChannels($result); $this->notifyDatabaseChannels($result); - $this->notifyDispatchingUser($result); $this->notifyMailChannels($result); $this->notifyWebhookChannels($result); } @@ -45,11 +58,6 @@ public function handle(SpeedtestCompleted $event): void */ private function notifyAppriseChannels(Result $result): void { - // Don't send Apprise notification if dispatched by a user or test is unhealthy. - if (filled($result->dispatched_by) || $result->healthy === false) { - return; - } - // Check if Apprise notifications are enabled. if (! $this->notificationSettings->apprise_enabled || ! $this->notificationSettings->apprise_on_speedtest_run) { return; @@ -97,11 +105,6 @@ private function notifyAppriseChannels(Result $result): void */ private function notifyDatabaseChannels(Result $result): void { - // Don't send database notification if dispatched by a user or test is unhealthy. - if (filled($result->dispatched_by) || $result->healthy === false) { - return; - } - // Check if database notifications are enabled. if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_speedtest_run) { return; @@ -125,10 +128,6 @@ private function notifyDatabaseChannels(Result $result): void */ private function notifyDispatchingUser(Result $result): void { - if (empty($result->dispatched_by) || ! $result->healthy) { - return; - } - $result->dispatchedBy->notify( FilamentNotification::make() ->title(__('results.speedtest_completed')) @@ -147,10 +146,6 @@ private function notifyDispatchingUser(Result $result): void */ private function notifyMailChannels(Result $result): void { - if (filled($result->dispatched_by) || $result->healthy === false) { - return; - } - if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_speedtest_run) { return; } @@ -172,11 +167,6 @@ private function notifyMailChannels(Result $result): void */ private function notifyWebhookChannels(Result $result): void { - // Don't send webhook if dispatched by a user or test is unhealthy. - if (filled($result->dispatched_by) || $result->healthy === false) { - return; - } - // Check if webhook notifications are enabled. if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_speedtest_run) { return; diff --git a/app/Listeners/ProcessFailedSpeedtest.php b/app/Listeners/ProcessFailedSpeedtest.php index 8ba154387..57fb0bc3a 100644 --- a/app/Listeners/ProcessFailedSpeedtest.php +++ b/app/Listeners/ProcessFailedSpeedtest.php @@ -16,10 +16,19 @@ public function handle(SpeedtestFailed $event): void { $result = $event->result; - $result->loadMissing(['dispatchedBy']); + // Notify the user who dispatched the speedtest. + if ($result->dispatched_by && $result->unscheduled) { + $result->loadMissing('dispatchedBy'); + + $this->notifyDispatchingUser($result); + } + + // Don't send notifications for unscheduled speedtests. + if ($result->unscheduled) { + return; + } // $this->notifyAppriseChannels($result); - $this->notifyDispatchingUser($result); } /** @@ -27,11 +36,6 @@ public function handle(SpeedtestFailed $event): void */ private function notifyAppriseChannels(Result $result): void { - // Don't send Apprise notification if dispatched by a user or test is unhealthy. - if (filled($result->dispatched_by) || ! $result->healthy) { - return; - } - // } @@ -40,10 +44,6 @@ private function notifyAppriseChannels(Result $result): void */ private function notifyDispatchingUser(Result $result): void { - if (empty($result->dispatched_by)) { - return; - } - $result->dispatchedBy->notify( Notification::make() ->title(__('results.speedtest_failed')) diff --git a/app/Listeners/ProcessUnhealthySpeedtest.php b/app/Listeners/ProcessUnhealthySpeedtest.php index 4f51e4267..54dd49669 100644 --- a/app/Listeners/ProcessUnhealthySpeedtest.php +++ b/app/Listeners/ProcessUnhealthySpeedtest.php @@ -33,11 +33,20 @@ public function handle(SpeedtestBenchmarkFailed $event): void { $result = $event->result; - $result->loadMissing(['dispatchedBy']); + // Notify the user who dispatched the speedtest. + if ($result->dispatched_by && $result->unscheduled) { + $result->loadMissing('dispatchedBy'); + + $this->notifyDispatchingUser($result); + } + + // Don't send notifications for unscheduled speedtests. + if ($result->unscheduled) { + return; + } $this->notifyAppriseChannels($result); $this->notifyDatabaseChannels($result); - $this->notifyDispatchingUser($result); $this->notifyMailChannels($result); $this->notifyWebhookChannels($result); } @@ -47,11 +56,6 @@ public function handle(SpeedtestBenchmarkFailed $event): void */ private function notifyAppriseChannels(Result $result): void { - // Don't send Apprise notification if dispatched by a user. - if (filled($result->dispatched_by)) { - return; - } - if (! $this->notificationSettings->apprise_enabled || ! $this->notificationSettings->apprise_on_threshold_failure) { return; } @@ -132,11 +136,6 @@ private function formatMetricValue(string $metric, Result $result): string */ private function notifyDatabaseChannels(Result $result): void { - // Don't send database notification if dispatched by a user. - if (filled($result->dispatched_by)) { - return; - } - // Check if database notifications are enabled. if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_threshold_failure) { return; @@ -160,10 +159,6 @@ private function notifyDatabaseChannels(Result $result): void */ private function notifyDispatchingUser(Result $result): void { - if (empty($result->dispatched_by)) { - return; - } - $result->dispatchedBy->notify( FilamentNotification::make() ->title(__('results.speedtest_benchmark_failed')) @@ -182,11 +177,6 @@ private function notifyDispatchingUser(Result $result): void */ private function notifyMailChannels(Result $result): void { - // Don't send mail if dispatched by a user. - if (filled($result->dispatched_by)) { - return; - } - // Check if mail notifications are enabled. if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_threshold_failure) { return; @@ -210,11 +200,6 @@ private function notifyMailChannels(Result $result): void */ private function notifyWebhookChannels(Result $result): void { - // Don't send webhook if dispatched by a user. - if (filled($result->dispatched_by)) { - return; - } - // Check if webhook notifications are enabled. if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_threshold_failure) { return; From 17eeafadf1784100d749bd951dca6cec0d609a44 Mon Sep 17 00:00:00 2001 From: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> Date: Fri, 26 Dec 2025 14:06:35 -0500 Subject: [PATCH 4/5] moved user notifications to a subscriber --- app/Listeners/ProcessCompletedSpeedtest.php | 25 ----- app/Listeners/ProcessFailedSpeedtest.php | 27 ------ app/Listeners/ProcessUnhealthySpeedtest.php | 25 ----- app/Listeners/UserNotificationSubscriber.php | 99 ++++++++++++++++++++ 4 files changed, 99 insertions(+), 77 deletions(-) create mode 100644 app/Listeners/UserNotificationSubscriber.php diff --git a/app/Listeners/ProcessCompletedSpeedtest.php b/app/Listeners/ProcessCompletedSpeedtest.php index 6255ab416..ae5d544e1 100644 --- a/app/Listeners/ProcessCompletedSpeedtest.php +++ b/app/Listeners/ProcessCompletedSpeedtest.php @@ -35,13 +35,6 @@ public function handle(SpeedtestCompleted $event): void return; } - // Notify the user who dispatched the speedtest. - if ($result->dispatched_by && $result->unscheduled) { - $result->loadMissing('dispatchedBy'); - - $this->notifyDispatchingUser($result); - } - // Don't send notifications for unscheduled speedtests. if ($result->unscheduled) { return; @@ -123,24 +116,6 @@ private function notifyDatabaseChannels(Result $result): void } } - /** - * Notify the user who dispatched the speedtest. - */ - private function notifyDispatchingUser(Result $result): void - { - $result->dispatchedBy->notify( - FilamentNotification::make() - ->title(__('results.speedtest_completed')) - ->actions([ - Action::make('view') - ->label(__('general.view')) - ->url(route('filament.admin.resources.results.index')), - ]) - ->success() - ->toDatabase(), - ); - } - /** * Notify mail channels. */ diff --git a/app/Listeners/ProcessFailedSpeedtest.php b/app/Listeners/ProcessFailedSpeedtest.php index 57fb0bc3a..3cc4d9cd8 100644 --- a/app/Listeners/ProcessFailedSpeedtest.php +++ b/app/Listeners/ProcessFailedSpeedtest.php @@ -4,8 +4,6 @@ use App\Events\SpeedtestFailed; use App\Models\Result; -use Filament\Actions\Action; -use Filament\Notifications\Notification; class ProcessFailedSpeedtest { @@ -16,13 +14,6 @@ public function handle(SpeedtestFailed $event): void { $result = $event->result; - // Notify the user who dispatched the speedtest. - if ($result->dispatched_by && $result->unscheduled) { - $result->loadMissing('dispatchedBy'); - - $this->notifyDispatchingUser($result); - } - // Don't send notifications for unscheduled speedtests. if ($result->unscheduled) { return; @@ -38,22 +29,4 @@ private function notifyAppriseChannels(Result $result): void { // } - - /** - * Notify the user who dispatched the speedtest. - */ - private function notifyDispatchingUser(Result $result): void - { - $result->dispatchedBy->notify( - Notification::make() - ->title(__('results.speedtest_failed')) - ->actions([ - Action::make('view') - ->label(__('general.view')) - ->url(route('filament.admin.resources.results.index')), - ]) - ->warning() - ->toDatabase(), - ); - } } diff --git a/app/Listeners/ProcessUnhealthySpeedtest.php b/app/Listeners/ProcessUnhealthySpeedtest.php index 54dd49669..f2e0d419c 100644 --- a/app/Listeners/ProcessUnhealthySpeedtest.php +++ b/app/Listeners/ProcessUnhealthySpeedtest.php @@ -33,13 +33,6 @@ public function handle(SpeedtestBenchmarkFailed $event): void { $result = $event->result; - // Notify the user who dispatched the speedtest. - if ($result->dispatched_by && $result->unscheduled) { - $result->loadMissing('dispatchedBy'); - - $this->notifyDispatchingUser($result); - } - // Don't send notifications for unscheduled speedtests. if ($result->unscheduled) { return; @@ -154,24 +147,6 @@ private function notifyDatabaseChannels(Result $result): void } } - /** - * Notify the user who dispatched the speedtest. - */ - private function notifyDispatchingUser(Result $result): void - { - $result->dispatchedBy->notify( - FilamentNotification::make() - ->title(__('results.speedtest_benchmark_failed')) - ->actions([ - Action::make('view') - ->label(__('general.view')) - ->url(route('filament.admin.resources.results.index')), - ]) - ->warning() - ->toDatabase(), - ); - } - /** * Notify mail channels. */ diff --git a/app/Listeners/UserNotificationSubscriber.php b/app/Listeners/UserNotificationSubscriber.php new file mode 100644 index 000000000..ee2c09adf --- /dev/null +++ b/app/Listeners/UserNotificationSubscriber.php @@ -0,0 +1,99 @@ +result; + + if (empty($result->dispatched_by)) { + return; + } + + $result->loadMissing('dispatchedBy'); + + Notification::make() + ->title(__('results.speedtest_completed')) + ->actions([ + Action::make('view') + ->label(__('general.view')) + ->url(route('filament.admin.resources.results.index')), + ]) + ->success() + ->sendToDatabase($result->dispatchedBy); + } + + /** + * Handle the event. + */ + public function handleBenchmarkFailed(SpeedtestBenchmarkFailed $event): void + { + $result = $event->result; + + if (empty($result->dispatched_by)) { + return; + } + + $result->loadMissing('dispatchedBy'); + + Notification::make() + ->title(__('results.speedtest_benchmark_failed')) + ->actions([ + Action::make('view') + ->label(__('general.view')) + ->url(route('filament.admin.resources.results.index')), + ]) + ->warning() + ->sendToDatabase($result->dispatchedBy); + } + + /** + * Handle the event. + */ + public function handleFailed(SpeedtestFailed $event): void + { + $result = $event->result; + + if (empty($result->dispatched_by)) { + return; + } + + $result->loadMissing('dispatchedBy'); + + Notification::make() + ->title(__('results.speedtest_failed')) + ->actions([ + Action::make('view') + ->label(__('general.view')) + ->url(route('filament.admin.resources.results.index')), + ]) + ->warning() + ->sendToDatabase($result->dispatchedBy); + } + + /** + * Register the listeners for the subscriber. + * + * @return array + */ + public function subscribe(Dispatcher $events): array + { + return [ + SpeedtestCompleted::class => 'handleCompleted', + SpeedtestBenchmarkFailed::class => 'handleBenchmarkFailed', + SpeedtestFailed::class => 'handleFailed', + ]; + } +} From edd0c9eb65a32ac777237fb7367c70cf131949e0 Mon Sep 17 00:00:00 2001 From: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> Date: Fri, 26 Dec 2025 14:40:26 -0500 Subject: [PATCH 5/5] prevent notifications for unscheduled speedtests to the dispatching user --- app/Listeners/UserNotificationSubscriber.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Listeners/UserNotificationSubscriber.php b/app/Listeners/UserNotificationSubscriber.php index ee2c09adf..4ff7dc7c8 100644 --- a/app/Listeners/UserNotificationSubscriber.php +++ b/app/Listeners/UserNotificationSubscriber.php @@ -46,6 +46,11 @@ public function handleBenchmarkFailed(SpeedtestBenchmarkFailed $event): void return; } + // Don't send notifications for unscheduled speedtests. + if ($result->unscheduled) { + return; + } + $result->loadMissing('dispatchedBy'); Notification::make()