diff --git a/app/Enums/ThresholdBreached.php b/app/Enums/ThresholdBreached.php new file mode 100644 index 000000000..b275e67fc --- /dev/null +++ b/app/Enums/ThresholdBreached.php @@ -0,0 +1,31 @@ + 'danger', + self::Passed => 'success', + self::NotChecked => 'warning', + }; + } + + public function getLabel(): ?string + { + return match ($this) { + self::Failed => 'Failed', + self::Passed => 'Passed', + self::NotChecked => 'NotChecked', + }; + } +} diff --git a/app/Filament/Exports/ResultExporter.php b/app/Filament/Exports/ResultExporter.php index 05f7ac2ee..901f0a105 100644 --- a/app/Filament/Exports/ResultExporter.php +++ b/app/Filament/Exports/ResultExporter.php @@ -103,6 +103,11 @@ public static function getColumns(): array }), ExportColumn::make('comments') ->enabledByDefault(false), + ExportColumn::make('threshold_breached') + ->enabledByDefault(false) + ->state(function (Result $record): string { + return $record->threshold_breached; + }), // ExportColumn::make('status'), // TODO: enable status when upgrading to PHP v8.3: https://php.watch/versions/8.3/dynamic-class-const-enum-member-syntax-support ExportColumn::make('scheduled') ->state(function (Result $record): string { diff --git a/app/Filament/Resources/ResultResource.php b/app/Filament/Resources/ResultResource.php index 005269bf6..e7837af63 100644 --- a/app/Filament/Resources/ResultResource.php +++ b/app/Filament/Resources/ResultResource.php @@ -4,6 +4,7 @@ use App\Actions\MigrateBadJsonResults; use App\Enums\ResultStatus; +use App\Enums\ThresholdBreached; use App\Filament\Exports\ResultExporter; use App\Filament\Resources\ResultResource\Pages; use App\Helpers\Number; @@ -311,6 +312,13 @@ public static function table(Table $table): Table ->badge() ->toggleable() ->sortable(), + Tables\Columns\TextColumn::make('threshold_breached') + ->label('Threshold') + ->badge() + ->color(fn (string $state): string => ThresholdBreached::from($state)->getColor()) + ->toggleable() + ->toggledHiddenByDefault() + ->sortable(), Tables\Columns\IconColumn::make('scheduled') ->boolean() ->toggleable() @@ -359,6 +367,10 @@ public static function table(Table $table): Table Tables\Filters\SelectFilter::make('status') ->multiple() ->options(ResultStatus::class), + Tables\Filters\SelectFilter::make('threshold_breached') + ->label('Threshold Status') + ->multiple() + ->options(ThresholdBreached::class), ]) ->actions([ Tables\Actions\ActionGroup::make([ diff --git a/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php b/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php index 3e3d591f9..d8b6edcd3 100644 --- a/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php +++ b/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php @@ -84,6 +84,9 @@ public function handle(): void 'status' => ResultStatus::Completed, ]); + // Ensure thresholds are checked and updated + $this->result->checkAndUpdateThresholds(); + SpeedtestCompleted::dispatch($this->result); } diff --git a/app/Models/Result.php b/app/Models/Result.php index 4965e49af..0a6ad2889 100644 --- a/app/Models/Result.php +++ b/app/Models/Result.php @@ -3,6 +3,8 @@ namespace App\Models; use App\Enums\ResultStatus; +use App\Helpers\Number; +use App\Settings\ThresholdSettings; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -14,6 +16,15 @@ class Result extends Model { use HasFactory, Prunable; + protected $fillable = [ + 'status', + 'download', + 'upload', + 'ping', + 'data', + 'threshold_breached', + ]; + /** * The attributes that aren't mass assignable. * @@ -290,4 +301,28 @@ protected function uploadlatencyiqm(): Attribute get: fn () => Arr::get($this->data, 'upload.latency.iqm'), ); } + + public function checkAndUpdateThresholds(): void + { + $thresholds = app(ThresholdSettings::class); + + // Determine if thresholds are enabled + $thresholdsEnabled = $thresholds->absolute_enabled; + + // Convert bits to Mbits if needed + $downloadInMbits = ! is_null($this->download) ? Number::bitsToMagnitude($this->download_bits, 2, 'mbit') : null; + $uploadInMbits = ! is_null($this->upload) ? Number::bitsToMagnitude($this->upload_bits, 2, 'mbit') : null; + + // Determine if thresholds are breached or NotChecked + $downloadBreached = $thresholdsEnabled && $downloadInMbits !== null && $downloadInMbits < $thresholds->absolute_download; + $uploadBreached = $thresholdsEnabled && $uploadInMbits !== null && $uploadInMbits < $thresholds->absolute_upload; + $pingBreached = $thresholdsEnabled && $this->ping !== null && $this->ping > $thresholds->absolute_ping; + + // Update only the threshold_breached field + $this->update([ + 'threshold_breached' => $thresholdsEnabled + ? ($downloadBreached || $uploadBreached || $pingBreached ? 'Failed' : 'Passed') + : 'NotChecked', + ]); + } } diff --git a/database/migrations/2024_08_19_115130_add_threshold_breached_to_results_table.php b/database/migrations/2024_08_19_115130_add_threshold_breached_to_results_table.php new file mode 100644 index 000000000..b714ac418 --- /dev/null +++ b/database/migrations/2024_08_19_115130_add_threshold_breached_to_results_table.php @@ -0,0 +1,27 @@ +string('threshold_breached')->default('NotChecked'); + }); + } + + public function down(): void + { + Schema::table('results', function (Blueprint $table) { + if (Schema::hasColumn('results', 'threshold_breached')) { + $table->dropColumn('threshold_breached'); + } + }); + } +};