From 375ecf650c844d613789190a50612d1d9438ad4d Mon Sep 17 00:00:00 2001 From: Henry Whitaker Date: Fri, 21 Aug 2020 22:56:38 +0100 Subject: [PATCH] Added conditional notifications - Absolute values - Percentage threshold --- app/Helpers/SpeedtestHelper.php | 74 ++++++++++++++++ app/Listeners/SpeedtestCompleteListener.php | 36 +++++++- ...testAbsoluteThresholdNotificationSlack.php | 86 +++++++++++++++++++ ...stPercentageThresholdNotificationSlack.php | 86 +++++++++++++++++++ ...add_conditional_notifications_settings.php | 83 ++++++++++++++++++ resources/js/components/Settings/Settings.js | 37 ++++++++ 6 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 app/Notifications/SpeedtestAbsoluteThresholdNotificationSlack.php create mode 100644 app/Notifications/SpeedtestPercentageThresholdNotificationSlack.php create mode 100644 database/migrations/2020_08_21_204656_add_conditional_notifications_settings.php diff --git a/app/Helpers/SpeedtestHelper.php b/app/Helpers/SpeedtestHelper.php index f41bf317..ffd77891 100644 --- a/app/Helpers/SpeedtestHelper.php +++ b/app/Helpers/SpeedtestHelper.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use InvalidArgumentException; use JsonException; class SpeedtestHelper { @@ -301,4 +302,77 @@ class SpeedtestHelper { 'msg' => 'There was an error backing up the database. No speedtests have been deleted.' ]; } + + /** + * Work out if a test is lower than the threshold for historic tests + * + * @param String $type + * @param Speedtest $test + * @return array + */ + public static function testIsLowerThanThreshold(String $type, Speedtest $test) + { + if($type == 'percentage') { + $avg = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload')) + ->where('failed', false) + ->get() + ->toArray()[0]; + + $threshold = SettingsHelper::get('threshold_alert_percentage')->value; + + if($threshold == '') { + return []; + } + + $errors = []; + + foreach($avg as $key => $value) { + if($key == 'ping') { + $threshold = (float)$value * (1 + ( $threshold / 100 )); + + if($test->$key > $threshold) { + array_push($errors, $key); + } + } else { + $threshold = (float)$value * (1 - ( $threshold / 100 )); + + if($test->$key < $threshold) { + array_push($errors, $key); + } + } + } + + return $errors; + } + + if($type == 'absolute') { + $thresholds = [ + 'download' => SettingsHelper::get('threshold_alert_absolute_download')->value, + 'upload' => SettingsHelper::get('threshold_alert_absolute_upload')->value, + 'ping' => SettingsHelper::get('threshold_alert_absolute_ping')->value, + ]; + + $errors = []; + + foreach($thresholds as $key => $value) { + if($value == '') { + continue; + } + + if($key == 'ping') { + if($test->$key > $value) { + array_push($errors, $key); + } + } else { + if($test->$key < $value) { + array_push($errors, $key); + } + } + } + + return $errors; + } + + throw new InvalidArgumentException(); + } } diff --git a/app/Listeners/SpeedtestCompleteListener.php b/app/Listeners/SpeedtestCompleteListener.php index b8a329c9..42158fbf 100644 --- a/app/Listeners/SpeedtestCompleteListener.php +++ b/app/Listeners/SpeedtestCompleteListener.php @@ -3,8 +3,11 @@ namespace App\Listeners; use App\Helpers\SettingsHelper; +use App\Helpers\SpeedtestHelper; +use App\Notifications\SpeedtestAbsoluteThresholdNotificationSlack; use App\Notifications\SpeedtestCompleteSlack; use App\Notifications\SpeedtestCompleteTelegram; +use App\Notifications\SpeedtestPercentageThresholdNotificationSlack; use Exception; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; @@ -32,9 +35,40 @@ class SpeedtestCompleteListener */ public function handle($event) { + Log::info('handling event'); + Log::info(SettingsHelper::get('threshold_alert_percentage_notifications')->value); + if((bool)SettingsHelper::get('threshold_alert_percentage_notifications')->value == true) { + $data = $event->speedtest; + $errors = SpeedtestHelper::testIsLowerThanThreshold('percentage', $data); + if(sizeof($errors) > 0) { + try { + Notification::route('slack', SettingsHelper::get('slack_webhook')->value) + ->notify(new SpeedtestPercentageThresholdNotificationSlack($errors)); + } catch(Exception $e) { + // + } + } + } + + if((bool)SettingsHelper::get('threshold_alert_absolute_notifications')->value == true) { + $data = $event->speedtest; + Log::info('absolute nots enabled'); + Log::info($data); + $errors = SpeedtestHelper::testIsLowerThanThreshold('absolute', $data); + Log::info($errors); + if(sizeof($errors) > 0) { + try { + Notification::route('slack', SettingsHelper::get('slack_webhook')->value) + ->notify(new SpeedtestAbsoluteThresholdNotificationSlack($errors)); + } catch(Exception $e) { + // + } + } + } + if(SettingsHelper::get('speedtest_notifications')->value == true) { $data = $event->speedtest; - if(SettingsHelper::get('slack_webhook')) { + if(SettingsHelper::get('slack_webhook')->value) { try { Notification::route('slack', SettingsHelper::get('slack_webhook')->value) ->notify(new SpeedtestCompleteSlack($data)); diff --git a/app/Notifications/SpeedtestAbsoluteThresholdNotificationSlack.php b/app/Notifications/SpeedtestAbsoluteThresholdNotificationSlack.php new file mode 100644 index 00000000..2965e0d3 --- /dev/null +++ b/app/Notifications/SpeedtestAbsoluteThresholdNotificationSlack.php @@ -0,0 +1,86 @@ +errors = $errors; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['slack']; + } + + /** + * Format slack notification + * + * @param mixed $notifiable + * @return SlackMessage + */ + public function toSlack($notifiable) + { + return (new SlackMessage) + ->warning() + ->attachment(function ($attachment) { + $attachment->title('Speedtest absolute threshold error') + ->content($this->formatMessage()); + }); + } + + /** + * Parse $this->errors and format message + * + * @return String + */ + public function formatMessage() + { + $msg = 'For the latest speedtest, the '; + + for($i = 0; $i < sizeof($this->errors); $i++) { + $key = $this->errors[$i]; + $msg = $msg . $key; + if(sizeof($this->errors) > 1 && $i < (sizeof($this->errors) - 1)) { + $msg = $msg . ', '; + } + } + + if($msg[-1] != '') { + $msg = $msg . ' '; + } + + if(sizeof($this->errors) > 1) { + $msg = $msg . 'values '; + } else { + $msg = $msg . 'value '; + } + + $msg = $msg . 'exceeded the absolute threshold'; + + return $msg; + } +} diff --git a/app/Notifications/SpeedtestPercentageThresholdNotificationSlack.php b/app/Notifications/SpeedtestPercentageThresholdNotificationSlack.php new file mode 100644 index 00000000..f86b7413 --- /dev/null +++ b/app/Notifications/SpeedtestPercentageThresholdNotificationSlack.php @@ -0,0 +1,86 @@ +errors = $errors; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['slack']; + } + + /** + * Format slack notification + * + * @param mixed $notifiable + * @return SlackMessage + */ + public function toSlack($notifiable) + { + return (new SlackMessage) + ->warning() + ->attachment(function ($attachment) { + $attachment->title('Speedtest percentage threshold error') + ->content($this->formatMessage()); + }); + } + + /** + * Parse $this->errors and format message + * + * @return String + */ + public function formatMessage() + { + $msg = 'For the latest speedtest, the '; + + for($i = 0; $i < sizeof($this->errors); $i++) { + $key = $this->errors[$i]; + $msg = $msg . $key; + if(sizeof($this->errors) > 1 && $i < (sizeof($this->errors) - 1)) { + $msg = $msg . ', '; + } + } + + if($msg[-1] != '') { + $msg = $msg . ' '; + } + + if(sizeof($this->errors) > 1) { + $msg = $msg . 'values '; + } else { + $msg = $msg . 'value '; + } + + $msg = $msg . 'exceeded the percentage threshold'; + + return $msg; + } +} diff --git a/database/migrations/2020_08_21_204656_add_conditional_notifications_settings.php b/database/migrations/2020_08_21_204656_add_conditional_notifications_settings.php new file mode 100644 index 00000000..8c36d2c2 --- /dev/null +++ b/database/migrations/2020_08_21_204656_add_conditional_notifications_settings.php @@ -0,0 +1,83 @@ + 'threshold_alert_percentage_notifications', + 'value' => false, + 'description' => 'Enable/disable theshold percentage notifications' + ]); + } + + if(!SettingsHelper::get('threshold_alert_percentage')) { + Setting::create([ + 'name' => 'threshold_alert_percentage', + 'value' => 15, + 'description' => 'When any value of a speedtest is x percent lower than the average, a notification will be sent.' + ]); + } + + if(!SettingsHelper::get('threshold_alert_absolute_notifications')) { + Setting::create([ + 'name' => 'threshold_alert_absolute_notifications', + 'value' => false, + 'description' => 'Enable/disable absolute theshold notifications' + ]); + } + + if(!SettingsHelper::get('threshold_alert_absolute_download')) { + Setting::create([ + 'name' => 'threshold_alert_absolute_download', + 'value' => '', + 'description' => 'When the download is lower than this value, a notification will be sent. Leave blank to disable' + ]); + } + + if(!SettingsHelper::get('threshold_alert_absolute_upload')) { + Setting::create([ + 'name' => 'threshold_alert_absolute_upload', + 'value' => '', + 'description' => 'When the upload is lower than this value, a notification will be sent. Leave blank to disable' + ]); + } + + if(!SettingsHelper::get('threshold_alert_absolute_ping')) { + Setting::create([ + 'name' => 'threshold_alert_absolute_ping', + 'value' => '', + 'description' => 'When the ping is higher than this value, a notification will be sent. Leave blank to disable' + ]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Setting::whereIn('name', [ + 'threshold_alert_percentage', + 'threshold_alert_absolute_download', + 'threshold_alert_absolute_upload', + 'threshold_alert_absolute_ping', + 'threshold_alert_percentage_notifications', + 'threshold_alert_absolute_notifications' + ])->delete(); + } +} diff --git a/resources/js/components/Settings/Settings.js b/resources/js/components/Settings/Settings.js index d2ac48a8..9c0bfcc0 100644 --- a/resources/js/components/Settings/Settings.js +++ b/resources/js/components/Settings/Settings.js @@ -158,6 +158,43 @@ export default class Settings extends Component { type: 'number', min: 0, max: 23 + }, + { + obj: { + id: (Math.floor(Math.random() * 10000) + 1), + name: "Conditional Notifications", + description: "" + }, + type: 'group', + children: [ + + ] + }, + { + obj: e.threshold_alert_percentage_notifications, + type: 'checkbox', + }, + { + obj: e.threshold_alert_percentage, + type: 'number', + min: 0, + max: 100 + }, + { + obj: e.threshold_alert_absolute_notifications, + type: 'checkbox', + }, + { + obj: e.threshold_alert_absolute_download, + type: 'number', + }, + { + obj: e.threshold_alert_absolute_upload, + type: 'number', + }, + { + obj: e.threshold_alert_absolute_ping, + type: 'number', } ]} />