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 afe43fa46..7a31f1bb4 100644 --- a/app/Filament/Exports/ResultExporter.php +++ b/app/Filament/Exports/ResultExporter.php @@ -107,6 +107,26 @@ public static function getColumns(): array }), ExportColumn::make('comments') ->enabledByDefault(false), + ExportColumn::make('threshold_breached_overall') + ->enabledByDefault(false) + ->state(function (Result $record): string { + return $record->threshold_breached_overall; + }), + ExportColumn::make('threshold_breached_download') + ->enabledByDefault(false) + ->state(function (Result $record): string { + return $record->threshold_breached_download; + }), + ExportColumn::make('threshold_breached_upload') + ->enabledByDefault(false) + ->state(function (Result $record): string { + return $record->threshold_breached_upload; + }), + ExportColumn::make('threshold_breached_ping') + ->enabledByDefault(false) + ->state(function (Result $record): string { + return $record->threshold_breached_ping; + }), // 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 e21fae587..2d8777f84 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; @@ -19,6 +20,7 @@ use Filament\Support\Enums\Alignment; use Filament\Tables; use Filament\Tables\Actions\Action; +use Filament\Tables\Enums\FiltersLayout; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Auth; @@ -310,6 +312,34 @@ public static function table(Table $table): Table ->badge() ->toggleable() ->sortable(), + Tables\Columns\TextColumn::make('threshold_breached_overall') + ->label('Overall Threshold') + ->badge() + ->color(fn (string $state): string => ThresholdBreached::from($state)->getColor()) + ->toggleable() + ->toggledHiddenByDefault() + ->sortable(), + Tables\Columns\TextColumn::make('threshold_breached_download') + ->label('Download Threshold') + ->badge() + ->color(fn (string $state): string => ThresholdBreached::from($state)->getColor()) + ->toggleable() + ->toggledHiddenByDefault() + ->sortable(), + Tables\Columns\TextColumn::make('threshold_breached_upload') + ->label('Upload Threshold') + ->badge() + ->color(fn (string $state): string => ThresholdBreached::from($state)->getColor()) + ->toggleable() + ->toggledHiddenByDefault() + ->sortable(), + Tables\Columns\TextColumn::make('threshold_breached_ping') + ->label('Ping Threshold') + ->badge() + ->color(fn (string $state): string => ThresholdBreached::from($state)->getColor()) + ->toggleable() + ->toggledHiddenByDefault() + ->sortable(), Tables\Columns\IconColumn::make('scheduled') ->boolean() ->toggleable() @@ -358,7 +388,11 @@ public static function table(Table $table): Table Tables\Filters\SelectFilter::make('status') ->multiple() ->options(ResultStatus::class), - ]) + Tables\Filters\SelectFilter::make('threshold_breached_overall') + ->label('Overall Threshold Status') + ->multiple() + ->options(ThresholdBreached::class), + ], layout: FiltersLayout::Modal) ->actions([ Tables\Actions\ActionGroup::make([ Action::make('view result') diff --git a/app/Jobs/CheckAndUpdateThresholds.php b/app/Jobs/CheckAndUpdateThresholds.php new file mode 100644 index 000000000..e5c7d1d2c --- /dev/null +++ b/app/Jobs/CheckAndUpdateThresholds.php @@ -0,0 +1,87 @@ +result = $result; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $thresholds = app(ThresholdSettings::class); + + // Determine if thresholds are enabled + $thresholdsEnabled = $thresholds->absolute_enabled; + + // Convert bits to Mbits if needed + $downloadInMbits = ! is_null($this->result->download) + ? Number::bitsToMagnitude($this->result->download_bits, 2, 'mbit') + : null; + $uploadInMbits = ! is_null($this->result->upload) + ? Number::bitsToMagnitude($this->result->upload_bits, 2, 'mbit') + : null; + + // Only check thresholds if they are enabled and not set to 0 + $downloadBreached = $thresholdsEnabled + && $thresholds->absolute_download > 0 + && $downloadInMbits !== null + && $downloadInMbits < $thresholds->absolute_download; + + $uploadBreached = $thresholdsEnabled + && $thresholds->absolute_upload > 0 + && $uploadInMbits !== null + && $uploadInMbits < $thresholds->absolute_upload; + + $pingBreached = $thresholdsEnabled + && $thresholds->absolute_ping > 0 + && $this->result->ping !== null + && $this->result->ping > $thresholds->absolute_ping; + + // Calculate individual statuses + $downloadStatus = $thresholdsEnabled && $thresholds->absolute_download > 0 + ? ($downloadBreached ? 'Failed' : 'Passed') + : 'NotChecked'; + + $uploadStatus = $thresholdsEnabled && $thresholds->absolute_upload > 0 + ? ($uploadBreached ? 'Failed' : 'Passed') + : 'NotChecked'; + + $pingStatus = $thresholdsEnabled && $thresholds->absolute_ping > 0 + ? ($pingBreached ? 'Failed' : 'Passed') + : 'NotChecked'; + + // Calculate the overall status + $overallStatus = $thresholdsEnabled + ? ($downloadBreached || $uploadBreached || $pingBreached ? 'Failed' : 'Passed') + : 'NotChecked'; + + // Update all relevant fields in the database + $this->result->update([ + 'threshold_breached_overall' => $overallStatus, + 'threshold_breached_download' => $downloadStatus, + 'threshold_breached_upload' => $uploadStatus, + 'threshold_breached_ping' => $pingStatus, + ]); + } +} diff --git a/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php b/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php index a8bd84b5e..511823b6c 100644 --- a/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php +++ b/app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php @@ -8,6 +8,7 @@ use App\Events\SpeedtestFailed; use App\Events\SpeedtestSkipped; use App\Helpers\Network; +use App\Jobs\CheckAndUpdateThresholds; use App\Models\Result; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; @@ -126,6 +127,9 @@ public function handle(): void 'status' => ResultStatus::Completed, ]); + // Ensure thresholds are checked and updated + CheckAndUpdateThresholds::dispatch($this->result); + SpeedtestCompleted::dispatch($this->result); } 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..ed6690a9b --- /dev/null +++ b/database/migrations/2024_08_19_115130_add_threshold_breached_to_results_table.php @@ -0,0 +1,36 @@ +string('threshold_breached_overall')->default('NotChecked'); + $table->string('threshold_breached_download')->default('NotChecked'); + $table->string('threshold_breached_upload')->default('NotChecked'); + $table->string('threshold_breached_ping')->default('NotChecked'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('results', function (Blueprint $table) { + $table->dropColumn([ + 'threshold_breached_overall', + 'threshold_breached_download', + 'threshold_breached_upload', + 'threshold_breached_ping', + ]); + }); + } +};