From 08f82f8a2e245efa8fcc8dd4197196668a8be891 Mon Sep 17 00:00:00 2001 From: "B.Fatih KOZ" Date: Tue, 15 Feb 2022 00:24:22 +0300 Subject: [PATCH] Admin and Discord Notification Improvements (#1407) * Backend changes Sorting for : Airlines, Subfleets, Type Ratings and exported Flights Blade : Added WYSIWYG editor to flight remarks/notes field Notifications : Mails > Added enabled/disabled settings for mails. Discord > Dashed out PirepPreFiled, re-enabled PirepStatusChanged with reduced messages * Update PirepStatusChanged.php * Update NotificationEventsHandler.php * in_array fix * Fix Discord Notifications * Discord Notifications Removed the pirep url from message as it is mostly private at phpvms side. Also removed the Flight Ident from fields 'cause it is being used in the title. Added the user avatar as thumbnail, and pirep filed message uses the airline logo as the main image. Even though the outgoing pirep status messages are reduced, it is still possible to enable/disable them from admin settings. Pirep Filed is always being sent (if the webhook is defined) * StyleFix --- app/Database/seeds/settings.yml | 35 ++++++++++ .../Controllers/Admin/AirlinesController.php | 2 +- .../Controllers/Admin/FlightController.php | 2 +- .../Controllers/Admin/SubfleetController.php | 2 +- .../Admin/TypeRatingController.php | 3 +- .../Channels/Discord/DiscordMessage.php | 67 ++++++++++++------- .../Messages/Broadcast/NewsAdded.php | 2 +- .../Messages/Broadcast/PirepFiled.php | 22 +++--- .../Messages/Broadcast/PirepStatusChanged.php | 41 ++++++++---- .../Messages/Broadcast/UserRegistered.php | 2 +- .../NotificationEventsHandler.php | 48 ++++++++++--- .../views/admin/flights/fields.blade.php | 13 +++- 12 files changed, 171 insertions(+), 68 deletions(-) diff --git a/app/Database/seeds/settings.yml b/app/Database/seeds/settings.yml index 24a87b32..cd8807da 100644 --- a/app/Database/seeds/settings.yml +++ b/app/Database/seeds/settings.yml @@ -410,6 +410,41 @@ options: '' type: text description: The Discord Webhook URL for private notifications +- key: notifications.discord_pirep_status + name: Discord Pirep Messages (Public) + group: notifications + value: true + options: '' + type: boolean + description: Pirep status messages (Only key events are being sent) +- key: notifications.mail_pirep_admin + name: Pirep Filed (Admin) + group: notifications + value: true + options: '' + type: boolean + description: Pirep filed mails sent to admins +- key: notifications.mail_pirep_user_ack + name: Pirep Accepted (Pilot) + group: notifications + value: true + options: '' + type: boolean + description: Pirep Accepted mails sent to pilots +- key: notifications.mail_pirep_user_rej + name: Pirep Rejected (Pilot) + group: notifications + value: true + options: '' + type: boolean + description: Pirep Rejected mails sent to pilots +- key: notifications.mail_news + name: News Mails + group: notifications + value: true + options: '' + type: boolean + description: News mails sent to all members - key: 'cron.random_id' name: 'Cron Randomized ID' group: 'cron' diff --git a/app/Http/Controllers/Admin/AirlinesController.php b/app/Http/Controllers/Admin/AirlinesController.php index afbc6104..774420c1 100644 --- a/app/Http/Controllers/Admin/AirlinesController.php +++ b/app/Http/Controllers/Admin/AirlinesController.php @@ -38,7 +38,7 @@ class AirlinesController extends Controller public function index(Request $request) { $this->airlineRepo->pushCriteria(new RequestCriteria($request)); - $airlines = $this->airlineRepo->all(); + $airlines = $this->airlineRepo->orderby('name', 'asc')->get(); return view('admin.airlines.index', [ 'airlines' => $airlines, diff --git a/app/Http/Controllers/Admin/FlightController.php b/app/Http/Controllers/Admin/FlightController.php index 783cc827..5e984b82 100644 --- a/app/Http/Controllers/Admin/FlightController.php +++ b/app/Http/Controllers/Admin/FlightController.php @@ -308,7 +308,7 @@ class FlightController extends Controller $where['airline_id'] = $airline_id; $file_name = 'flights-'.$airline_id.'.csv'; } - $flights = $this->flightRepo->where($where)->get(); + $flights = $this->flightRepo->where($where)->orderBy('airline_id')->orderBy('flight_number')->orderBy('route_code')->orderBy('route_leg')->get(); $path = $exporter->exportFlights($flights); return response()->download($path, $file_name, ['content-type' => 'text/csv'])->deleteFileAfterSend(true); diff --git a/app/Http/Controllers/Admin/SubfleetController.php b/app/Http/Controllers/Admin/SubfleetController.php index 3410ca03..7e7a64ba 100644 --- a/app/Http/Controllers/Admin/SubfleetController.php +++ b/app/Http/Controllers/Admin/SubfleetController.php @@ -83,7 +83,7 @@ class SubfleetController extends Controller public function index(Request $request) { $this->subfleetRepo->with(['airline'])->pushCriteria(new RequestCriteria($request)); - $subfleets = $this->subfleetRepo->all(); + $subfleets = $this->subfleetRepo->orderby('name', 'asc')->get(); return view('admin.subfleets.index', [ 'subfleets' => $subfleets, diff --git a/app/Http/Controllers/Admin/TypeRatingController.php b/app/Http/Controllers/Admin/TypeRatingController.php index dbe4f630..5a3727f3 100644 --- a/app/Http/Controllers/Admin/TypeRatingController.php +++ b/app/Http/Controllers/Admin/TypeRatingController.php @@ -32,7 +32,7 @@ class TypeRatingController extends Controller public function index(Request $request) { $this->typeratingRepo->pushCriteria(new RequestCriteria($request)); - $typeratings = $this->typeratingRepo->all(); + $typeratings = $this->typeratingRepo->orderby('type', 'asc')->get(); return view('admin.typeratings.index', [ 'typeratings' => $typeratings, @@ -50,7 +50,6 @@ class TypeRatingController extends Controller $model = $this->typeratingRepo->create($input); Flash::success('Type Rating saved successfully.'); - // Cache::forget(config('cache.keys.RANKS_PILOT_LIST.key')); return redirect(route('admin.typeratings.edit', [$model->id])); } diff --git a/app/Notifications/Channels/Discord/DiscordMessage.php b/app/Notifications/Channels/Discord/DiscordMessage.php index 3875e9db..18c24440 100644 --- a/app/Notifications/Channels/Discord/DiscordMessage.php +++ b/app/Notifications/Channels/Discord/DiscordMessage.php @@ -11,14 +11,16 @@ use Carbon\Carbon; */ class DiscordMessage { - const COLOR_SUCCESS = '#0B6623'; - const COLOR_WARNING = '#FD6A02'; - const COLOR_ERROR = '#ED2939'; + const COLOR_SUCCESS = '0B6623'; + const COLOR_WARNING = 'FD6A02'; + const COLOR_ERROR = 'ED2939'; public $webhook_url; protected $title; protected $url; + protected $thumbnail = []; + protected $image = []; protected $description; protected $timestamp; protected $footer; @@ -26,30 +28,43 @@ class DiscordMessage protected $author = []; protected $fields = []; - /** - * Supply the webhook URL that this should be going to - */ + // Supply the webhook URL that this should be going to public function webhook(string $url): self { $this->webhook_url = $url; return $this; } - /** - * Title of the notification - */ + // Title of the embed public function title(string $title): self { $this->title = $title; return $this; } + // URL of the Title public function url(string $url): self { $this->url = $url; return $this; } + // Thumbnail image (placed right side of embed) + // ['url' => ''] + public function thumbnail(array $thumbnail): self + { + $this->thumbnail = $thumbnail; + return $this; + } + + // Main image (placed bottom of embed) + // ['url' => ''] + public function image(array $image): self + { + $this->image = $image; + return $this; + } + /** * @param array|string $descriptionLines */ @@ -63,22 +78,15 @@ class DiscordMessage return $this; } - /** - * Set the author information: - * [ - * 'name' => '', - * 'url' => '', - * 'icon_url' => '', - */ + // Author details + // ['name' => '', 'url' => '', 'icon_url' => ''] public function author(array $author): self { $this->author = $author; return $this; } - /** - * Set the fields - */ + // Fields public function fields(array $fields): self { $this->fields = []; @@ -99,32 +107,45 @@ class DiscordMessage return $this; } + // Fixed Success Color public function success(): self { - $this->color = static::COLOR_SUCCESS; + $this->color = hexdec('0B6623'); // static::COLOR_SUCCESS; return $this; } + // Fixed Warning public function warning(): self { - $this->color = static::COLOR_WARNING; + $this->color = hexdec('FD6A02'); // static::COLOR_WARNING; return $this; } + // Fixed Error public function error(): self { - $this->color = static::COLOR_ERROR; + $this->color = hexdec('ED2939'); // static::COLOR_ERROR; + return $this; + } + + // Custom Color + public function color(string $embed_color): self + { + $this->color = hexdec($embed_color); return $this; } public function toArray(): array { $embeds = [ + 'type' => 'rich', + 'color' => $this->color, 'title' => $this->title, 'url' => $this->url, - 'type' => 'rich', + 'thumbnail' => $this->thumbnail, 'description' => $this->description, 'author' => $this->author, + 'image' => $this->image, 'timestamp' => Carbon::now('UTC'), ]; diff --git a/app/Notifications/Messages/Broadcast/NewsAdded.php b/app/Notifications/Messages/Broadcast/NewsAdded.php index fd57310f..1f96bfe2 100644 --- a/app/Notifications/Messages/Broadcast/NewsAdded.php +++ b/app/Notifications/Messages/Broadcast/NewsAdded.php @@ -31,7 +31,7 @@ class NewsAdded extends Notification implements ShouldQueue public function toDiscordChannel($news): ?DiscordMessage { $dm = new DiscordMessage(); - return $dm->url(setting('notifications.discord_public_webhook_url')) + return $dm->webhook(setting('notifications.discord_public_webhook_url')) ->success() ->title('News: '.$news->subject) ->author([ diff --git a/app/Notifications/Messages/Broadcast/PirepFiled.php b/app/Notifications/Messages/Broadcast/PirepFiled.php index ef23123c..0c7fb170 100644 --- a/app/Notifications/Messages/Broadcast/PirepFiled.php +++ b/app/Notifications/Messages/Broadcast/PirepFiled.php @@ -43,11 +43,10 @@ class PirepFiled extends Notification implements ShouldQueue { $title = 'Flight '.$pirep->ident.' Filed'; $fields = [ - 'Flight' => $pirep->ident, - 'Departure Airport' => $pirep->dpt_airport_id, - 'Arrival Airport' => $pirep->arr_airport_id, - 'Equipment' => $pirep->aircraft->ident, - 'Flight Time' => Time::minutesToTimeString($pirep->flight_time), + 'Dep.Airport' => $pirep->dpt_airport_id, + 'Arr.Airport' => $pirep->arr_airport_id, + 'Equipment' => $pirep->aircraft->ident, + 'Flight Time' => Time::minutesToTimeString($pirep->flight_time), ]; if ($pirep->distance) { @@ -63,16 +62,19 @@ class PirepFiled extends Notification implements ShouldQueue } } + // User avatar, somehow $pirep->user->resolveAvatarUrl() is not being accepted by Discord as thumbnail + $user_avatar = !empty($pirep->user->avatar) ? $pirep->user->avatar->url : $pirep->user->gravatar(256); + $dm = new DiscordMessage(); - return $dm->url(setting('notifications.discord_public_webhook_url')) + return $dm->webhook(setting('notifications.discord_public_webhook_url')) ->success() ->title($title) ->description($pirep->user->discord_id ? 'Flight by <@'.$pirep->user->discord_id.'>' : '') - ->url(route('frontend.pireps.show', [$pirep->id])) + ->thumbnail(['url' => $user_avatar]) + ->image(['url' => $pirep->airline->logo]) ->author([ - 'name' => $pirep->user->ident.' - '.$pirep->user->name_private, - 'url' => route('frontend.profile.show', [$pirep->user_id]), - 'icon_url' => $pirep->user->resolveAvatarUrl(), + 'name' => $pirep->user->ident.' - '.$pirep->user->name_private, + 'url' => route('frontend.profile.show', [$pirep->user_id]), ]) ->fields($fields); } diff --git a/app/Notifications/Messages/Broadcast/PirepStatusChanged.php b/app/Notifications/Messages/Broadcast/PirepStatusChanged.php index ef923002..d83f60f8 100644 --- a/app/Notifications/Messages/Broadcast/PirepStatusChanged.php +++ b/app/Notifications/Messages/Broadcast/PirepStatusChanged.php @@ -9,11 +9,9 @@ use App\Notifications\Channels\Discord\DiscordMessage; use App\Notifications\Channels\Discord\DiscordWebhook; use App\Support\Units\Distance; use App\Support\Units\Time; -use function config; use Illuminate\Contracts\Queue\ShouldQueue; use PhpUnitsOfMeasure\Exception\NonNumericValue; use PhpUnitsOfMeasure\Exception\NonStringUnitName; -use function route; /** * Send the PIREP accepted message to a particular user, can also be sent to Discord @@ -75,14 +73,16 @@ class PirepStatusChanged extends Notification implements ShouldQueue */ public function toDiscordChannel($pirep): ?DiscordMessage { - $title = 'Flight '.$pirep->ident.' '.self::$verbs[$pirep->status]; + if (empty(setting('notifications.discord_public_webhook_url'))) { + return null; + } + $title = 'Flight '.$pirep->ident.' '.self::$verbs[$pirep->status]; $fields = [ - 'Flight' => $pirep->ident, - 'Departure Airport' => $pirep->dpt_airport_id, - 'Arrival Airport' => $pirep->arr_airport_id, - 'Equipment' => $pirep->aircraft->ident, - 'Flight Time' => Time::minutesToTimeString($pirep->flight_time), + 'Dep.Airport' => $pirep->dpt_airport_id, + 'Arr.Airport' => $pirep->arr_airport_id, + 'Equipment' => $pirep->aircraft->ident, + 'Flight Time' => Time::minutesToTimeString($pirep->flight_time), ]; // Show the distance, but include the planned distance if it's been set @@ -109,16 +109,29 @@ class PirepStatusChanged extends Notification implements ShouldQueue } } + // User avatar, somehow $pirep->user->resolveAvatarUrl() is not being accepted by Discord as thumbnail + $user_avatar = !empty($pirep->user->avatar) ? $pirep->user->avatar->url : $pirep->user->gravatar(256); + + // Proper coloring for the messages + // Pirep Filed > success, normals > warning, non-normals > error + $danger_types = [ + PirepStatus::GRND_RTRN, + PirepStatus::DIVERTED, + PirepStatus::CANCELLED, + PirepStatus::PAUSED, + PirepStatus::EMERG_DESCENT, + ]; + $color = in_array($pirep->status, $danger_types, true) ? 'ED2939' : 'FD6A02'; + $dm = new DiscordMessage(); - return $dm->url(setting('notifications.discord_public_webhook_url')) - ->success() + return $dm->webhook(setting('notifications.discord_public_webhook_url')) + ->color($color) ->title($title) ->description($pirep->user->discord_id ? 'Flight by <@'.$pirep->user->discord_id.'>' : '') - ->url(route('frontend.pireps.show', [$pirep->id])) + ->thumbnail(['url' => $user_avatar]) ->author([ - 'name' => $pirep->user->ident.' - '.$pirep->user->name_private, - 'url' => route('frontend.profile.show', [$pirep->user_id]), - 'icon_url' => $pirep->user->resolveAvatarUrl(), + 'name' => $pirep->user->ident.' - '.$pirep->user->name_private, + 'url' => route('frontend.profile.show', [$pirep->user_id]), ]) ->fields($fields); } diff --git a/app/Notifications/Messages/Broadcast/UserRegistered.php b/app/Notifications/Messages/Broadcast/UserRegistered.php index 840c4e6a..8eea22c5 100644 --- a/app/Notifications/Messages/Broadcast/UserRegistered.php +++ b/app/Notifications/Messages/Broadcast/UserRegistered.php @@ -49,7 +49,7 @@ class UserRegistered extends Notification implements ShouldQueue public function toDiscordChannel($notifiable): ?DiscordMessage { $dm = new DiscordMessage(); - return $dm->url(setting('notifications.discord_private_webhook_url')) + return $dm->webhook(setting('notifications.discord_private_webhook_url')) ->success() ->title('New User Registered: '.$this->user->ident) ->author([ diff --git a/app/Notifications/NotificationEventsHandler.php b/app/Notifications/NotificationEventsHandler.php index bd0199e9..f31ad1fa 100644 --- a/app/Notifications/NotificationEventsHandler.php +++ b/app/Notifications/NotificationEventsHandler.php @@ -11,6 +11,7 @@ use App\Events\PirepRejected; use App\Events\PirepStatusChange; use App\Events\UserRegistered; use App\Events\UserStateChanged; +use App\Models\Enums\PirepStatus; use App\Models\Enums\UserState; use App\Models\User; use App\Notifications\Messages\UserRejected; @@ -161,7 +162,7 @@ class NotificationEventsHandler extends Listener } /** - * Prefile notification + * Prefile notification. Disabled intentionally, No need to send it to Discord */ public function onPirepPrefile(PirepPrefiled $event): void { @@ -170,16 +171,33 @@ class NotificationEventsHandler extends Listener /* * Broadcast notifications */ - Notification::send([$event->pirep], new Messages\Broadcast\PirepPrefiled($event->pirep)); + // Notification::send([$event->pirep], new Messages\Broadcast\PirepPrefiled($event->pirep)); } /** - * Status Change notification. Disabled for now, too noisy + * Status Change notification. + * Reduced the messages (Boarding, Pushback, TakeOff, Landing and non-normals only) + * If needed array can be tied to a setting at admin side for further customization */ public function onPirepStatusChange(PirepStatusChange $event): void { - // Log::info('NotificationEvents::onPirepStatusChange: '.$event->pirep->id.' prefiled'); - // Notification::send([$event->pirep], new Messages\Discord\PirepStatusChanged($event->pirep)); + Log::info('NotificationEvents::onPirepStatusChange: '.$event->pirep->id.' status changed'); + + $message_types = [ + PirepStatus::BOARDING, + PirepStatus::PUSHBACK_TOW, + PirepStatus::GRND_RTRN, + PirepStatus::TAKEOFF, + PirepStatus::LANDED, + PirepStatus::DIVERTED, + PirepStatus::CANCELLED, + PirepStatus::PAUSED, + PirepStatus::EMERG_DESCENT, + ]; + + if (setting('notifications.discord_pirep_status', true) && in_array($event->pirep->status, $message_types, true)) { + Notification::send([$event->pirep], new Messages\Broadcast\PirepStatusChanged($event->pirep)); + } } /** @@ -190,7 +208,9 @@ class NotificationEventsHandler extends Listener public function onPirepFile(PirepFiled $event): void { Log::info('NotificationEvents::onPirepFile: '.$event->pirep->id.' filed'); - $this->notifyAdmins(new Messages\PirepFiled($event->pirep)); + if (setting('notifications.mail_pirep_admin', true)) { + $this->notifyAdmins(new Messages\PirepFiled($event->pirep)); + } /* * Broadcast notifications @@ -205,8 +225,10 @@ class NotificationEventsHandler extends Listener */ public function onPirepAccepted(PirepAccepted $event): void { - Log::info('NotificationEvents::onPirepAccepted: '.$event->pirep->id.' accepted'); - $this->notifyUser($event->pirep->user, new Messages\PirepAccepted($event->pirep)); + if (setting('notifications.mail_pirep_user_ack', true)) { + Log::info('NotificationEvents::onPirepAccepted: '.$event->pirep->id.' accepted'); + $this->notifyUser($event->pirep->user, new Messages\PirepAccepted($event->pirep)); + } } /** @@ -216,8 +238,10 @@ class NotificationEventsHandler extends Listener */ public function onPirepRejected(PirepRejected $event): void { - Log::info('NotificationEvents::onPirepRejected: '.$event->pirep->id.' rejected'); - $this->notifyUser($event->pirep->user, new Messages\PirepRejected($event->pirep)); + if (setting('notifications.mail_pirep_user_rej', true)) { + Log::info('NotificationEvents::onPirepRejected: '.$event->pirep->id.' rejected'); + $this->notifyUser($event->pirep->user, new Messages\PirepRejected($event->pirep)); + } } /** @@ -228,7 +252,9 @@ class NotificationEventsHandler extends Listener public function onNewsAdded(NewsAdded $event): void { Log::info('NotificationEvents::onNewsAdded'); - $this->notifyAllUsers(new Messages\NewsAdded($event->news)); + if (setting('notifications.mail_news', true)) { + $this->notifyAllUsers(new Messages\NewsAdded($event->news)); + } /* * Broadcast notifications diff --git a/resources/views/admin/flights/fields.blade.php b/resources/views/admin/flights/fields.blade.php index e1adabfa..f011eecf 100644 --- a/resources/views/admin/flights/fields.blade.php +++ b/resources/views/admin/flights/fields.blade.php @@ -174,7 +174,6 @@ -
@@ -260,8 +259,9 @@
{{ Form::textarea('notes', null, [ - 'class' => 'form-control input-text', - 'style' => 'padding: 10px', + 'id' => 'editor', + 'class' => 'editor', + 'style' => 'padding: 5px', ]) }}

{{ $errors->first('notes') }}

@@ -288,3 +288,10 @@
+@section('scripts') + @parent + + +@endsection \ No newline at end of file