From d5769c88090bc182693ce078235c0a45a4fd7f9e Mon Sep 17 00:00:00 2001 From: Yann Labour Date: Tue, 24 Aug 2021 21:55:59 +0400 Subject: [PATCH] fixes #58 flagged rule notifications flagged rules mail notifications and reminders --- app/Console/Commands/FlaggedRulesReminder.php | 54 ++++++++++ app/Console/Kernel.php | 5 + app/Features/Rules/FlaggedCollector.php | 38 +++++++ ...eEditors.php => NotifyRuleContributor.php} | 6 +- app/Listeners/Rules/NotifyTeamOwner.php | 13 ++- app/Models/ClientAccount.php | 2 + app/Models/Rule.php | 14 +++ app/Models/RuleTerm.php | 9 ++ app/Models/Taxonomy.php | 2 + app/Models/Team.php | 4 + app/Models/Term.php | 2 + app/Models/User.php | 2 + app/Notifications/FlaggedRuleNotification.php | 76 ++++++++++++++ .../FlaggedRulesReminderNotification.php | 98 +++++++++++++++++++ app/Providers/EventServiceProvider.php | 2 +- .../JobClientAccountMatcher.php | 2 +- composer.json | 1 + composer.lock | 61 +++++++++++- config/snooze.php | 45 +++++++++ 19 files changed, 429 insertions(+), 7 deletions(-) create mode 100644 app/Console/Commands/FlaggedRulesReminder.php create mode 100644 app/Features/Rules/FlaggedCollector.php rename app/Listeners/Rules/{NotifyRuleEditors.php => NotifyRuleContributor.php} (64%) create mode 100644 app/Notifications/FlaggedRuleNotification.php create mode 100644 app/Notifications/FlaggedRulesReminderNotification.php create mode 100644 config/snooze.php diff --git a/app/Console/Commands/FlaggedRulesReminder.php b/app/Console/Commands/FlaggedRulesReminder.php new file mode 100644 index 00000000..e84c326e --- /dev/null +++ b/app/Console/Commands/FlaggedRulesReminder.php @@ -0,0 +1,54 @@ +handle(); + + foreach($flagged_collector->users_rules_dict as $user_id => $user_rules_list) { + /** @var User $user */ + $user = $user_rules_list['user']; + $this->info('notifying ' . $user->name); + + $user->notify(new FlaggedRulesReminderNotification($user_rules_list['rules'])); + + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 5b275269..98d52ea9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,6 +2,7 @@ namespace App\Console; +use App\Console\Commands\FlaggedRulesReminder; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Laravel\Nova\Trix\PruneStaleAttachments; @@ -30,6 +31,10 @@ protected function schedule(Schedule $schedule) //(new PruneStaleAttachments)(); })->daily(); + $schedule->call(function() { + (new FlaggedRulesReminder)(); + })->weeklyOn(1, '8:00'); + $schedule->command('cache:clear')->lastDayOfMonth(); $schedule->command('cache:warmup')->hourly(); } diff --git a/app/Features/Rules/FlaggedCollector.php b/app/Features/Rules/FlaggedCollector.php new file mode 100644 index 00000000..a9f66cb4 --- /dev/null +++ b/app/Features/Rules/FlaggedCollector.php @@ -0,0 +1,38 @@ +with(['contributors'])->get(); + $users_rules_dict = []; + + foreach ($rules as $rule) { + foreach ($rule->contributors as $contributor) { + if (!isset($users_rules_dict[$contributor->id])) { + $users_rules_dict[$contributor->id] = [ + 'user' => $contributor, + 'rules' => [], + ]; + } + + $users_rules_dict[$contributor->id]['rules'][] = $rule; + + } + } + + $this->users_rules_dict = $users_rules_dict; + + return $this; + } + +} diff --git a/app/Listeners/Rules/NotifyRuleEditors.php b/app/Listeners/Rules/NotifyRuleContributor.php similarity index 64% rename from app/Listeners/Rules/NotifyRuleEditors.php rename to app/Listeners/Rules/NotifyRuleContributor.php index b5b3fa22..65dd20b6 100644 --- a/app/Listeners/Rules/NotifyRuleEditors.php +++ b/app/Listeners/Rules/NotifyRuleContributor.php @@ -3,10 +3,12 @@ namespace App\Listeners\Rules; use App\Events\Rules\Flagged; +use App\Notifications\FlaggedRuleNotification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Support\Facades\Notification; -class NotifyRuleEditors +class NotifyRuleContributor implements ShouldQueue { /** * Create the event listener. @@ -26,6 +28,6 @@ public function __construct() */ public function handle(Flagged $event) { - // + Notification::send($event->rule->contributors, new FlaggedRuleNotification($event->rule)); } } diff --git a/app/Listeners/Rules/NotifyTeamOwner.php b/app/Listeners/Rules/NotifyTeamOwner.php index 34861493..90e57368 100644 --- a/app/Listeners/Rules/NotifyTeamOwner.php +++ b/app/Listeners/Rules/NotifyTeamOwner.php @@ -3,10 +3,12 @@ namespace App\Listeners\Rules; use App\Events\Rules\Flagged; +use App\Notifications\FlaggedRuleNotification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Support\Facades\Notification; -class NotifyTeamOwner +class NotifyTeamOwner implements ShouldQueue { /** * Create the event listener. @@ -26,6 +28,13 @@ public function __construct() */ public function handle(Flagged $event) { - // + Notification::send( + $event->rule->contributorTeams() + ->with('owner') + ->get() + ->pluck('owner') + ->whereNotIn('email', $event->rule->contributors->pluck('email')), + new FlaggedRuleNotification($event->rule) + ); } } diff --git a/app/Models/ClientAccount.php b/app/Models/ClientAccount.php index 5a80e09c..437cccfa 100644 --- a/app/Models/ClientAccount.php +++ b/app/Models/ClientAccount.php @@ -40,6 +40,8 @@ * @property-read int|null $child_taxonomies_count * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Taxonomy[] $root_taxonomies * @property-read int|null $root_taxonomies_count + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Team[] $teams + * @property-read int|null $teams_count */ class ClientAccount extends Model { diff --git a/app/Models/Rule.php b/app/Models/Rule.php index 0bd7be7c..a07b42d1 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -57,6 +57,14 @@ * @method static Builder|Rule whereNotState(string $column, $states) * @method static Builder|Rule whereState($value) * @method static Builder|Rule forClient(\App\Models\ClientAccount $clientAccount) + * @property-read mixed $dag_id + * @property-read \Illuminate\Database\Eloquent\Collection|\Altek\Accountant\Models\Ledger[] $ledgers + * @property-read int|null $ledgers_count + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Team[] $teams + * @property-read int|null $teams_count + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\User[] $users + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\User[] $contributors + * @property-read int|null $users_count */ class Rule extends Model implements Recordable { @@ -273,4 +281,10 @@ public function getDagIdAttribute() { return str_pad($this->id, 6, '0', STR_PAD_LEFT) . 'D'; } + + + public function getUrlAttribute() + { + return route('pm.client-account.rules.edit', [$this->clientAccount->slug, $this->id]); + } } diff --git a/app/Models/RuleTerm.php b/app/Models/RuleTerm.php index 8ace735d..32bac3ff 100644 --- a/app/Models/RuleTerm.php +++ b/app/Models/RuleTerm.php @@ -14,6 +14,15 @@ * * @property int $id * @property int $taxonomy_id + * @property int $rule_id + * @property int $term_id + * @method static \Illuminate\Database\Eloquent\Builder|RuleTerm newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|RuleTerm newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|RuleTerm query() + * @method static \Illuminate\Database\Eloquent\Builder|RuleTerm whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|RuleTerm whereRuleId($value) + * @method static \Illuminate\Database\Eloquent\Builder|RuleTerm whereTermId($value) + * @mixin \Eloquent */ class RuleTerm extends Pivot // implements Recordable { diff --git a/app/Models/Taxonomy.php b/app/Models/Taxonomy.php index cb0c0f3a..262f36f2 100644 --- a/app/Models/Taxonomy.php +++ b/app/Models/Taxonomy.php @@ -48,6 +48,8 @@ * @property-read int|null $mappings_count * @method static \Database\Factories\TaxonomyFactory factory(...$parameters) * @method static Builder|Taxonomy children() + * @property-read \Illuminate\Database\Eloquent\Collection|\Altek\Accountant\Models\Ledger[] $ledgers + * @property-read int|null $ledgers_count */ class Taxonomy extends Model implements Recordable { diff --git a/app/Models/Team.php b/app/Models/Team.php index 2e6e577e..c5f75b6e 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -35,6 +35,10 @@ * @property int $client_account_id * @method static \Illuminate\Database\Eloquent\Builder|Team whereClientAccountId($value) * @method static Builder|Team personal($personal = true) + * @property string|null $region + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\User[] $rules + * @property-read int|null $rules_count + * @method static Builder|Team whereRegion($value) */ class Team extends JetstreamTeam { diff --git a/app/Models/Term.php b/app/Models/Term.php index d748f374..33d04391 100644 --- a/app/Models/Term.php +++ b/app/Models/Term.php @@ -34,6 +34,8 @@ * @property-read int|null $rules_count * @method static \Illuminate\Database\Eloquent\Builder|Term whereDeletedAt($value) * @method static \Database\Factories\TermFactory factory(...$parameters) + * @property-read \Illuminate\Database\Eloquent\Collection|\Altek\Accountant\Models\Ledger[] $ledgers + * @property-read int|null $ledgers_count */ class Term extends Model implements Recordable { diff --git a/app/Models/User.php b/app/Models/User.php index 922fa1d3..2220ac69 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -72,6 +72,8 @@ * @method static \Illuminate\Database\Eloquent\Builder|User whereMobilePhone($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereOfficeLocation($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereSurname($value) + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Rule[] $rules + * @property-read int|null $rules_count */ class User extends Authenticatable implements Identifiable { diff --git a/app/Notifications/FlaggedRuleNotification.php b/app/Notifications/FlaggedRuleNotification.php new file mode 100644 index 00000000..2ceeb471 --- /dev/null +++ b/app/Notifications/FlaggedRuleNotification.php @@ -0,0 +1,76 @@ +rule; + $last_reason_key = array_key_last($rule->metadata['flag_reason']); + $flag_reason = $rule->metadata['flag_reason'][$last_reason_key]; + + $subject = 'Rule flagged: '.Str::limit($rule->name, 20); + + return (new MailMessage) + ->greeting('Hello '.$notifiable->given_name) + ->subject($subject) + ->line('The following flagged rule needs your attention:') + ->line(new HtmlString( + sprintf('

%s
The flag reason was: "%s" (%s on %s)


', + $rule->url, '['.$rule->dag_id.'] '.$rule->name, + $flag_reason['reason'], $flag_reason['user'], $flag_reason['date'] + ) + ))->salutation(new HtmlString('Regards,
The Dagobah Team')); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/app/Notifications/FlaggedRulesReminderNotification.php b/app/Notifications/FlaggedRulesReminderNotification.php new file mode 100644 index 00000000..dc094ffe --- /dev/null +++ b/app/Notifications/FlaggedRulesReminderNotification.php @@ -0,0 +1,98 @@ +rules) > 1 ? 'rules' : 'rule: ' . Str::limit($this->rules[0]->name, 20)); + $message = (new MailMessage) + ->greeting('Hello ' . $notifiable->given_name) + ->subject($subject) + ->line(sprintf('The following flagged %s your attention:', count($this->rules) > 1 ? 'rules need' : 'rule needs')); + + $published_rules = collect($this->rules)->where('state', PublishedState::$name); + $unpublished_rules = collect($this->rules)->where('state', '!=', PublishedState::$name); + + /** @var Rule $rule */ + if(count($published_rules) && count($unpublished_rules)) { + $message->line(new HtmlString('Published Rules')); + } + + $this->addRules($published_rules, $message); + + if(count($published_rules) && count($unpublished_rules)) { + $message->line(new HtmlString('Unpublished Rules')); + } + + $this->addRules($unpublished_rules, $message); + + return $message->salutation(new HtmlString('Regards,
The Dagobah Team')); + } + + public function addRules($rules, &$message) { + foreach ($rules as $rule) { + $last_reason_key = array_key_last($rule->metadata['flag_reason']); + $flag_reason = $rule->metadata['flag_reason'][$last_reason_key]; + + $message->line(new HtmlString( + sprintf('

%s
The flag reason was: "%s" (%s on %s)


', + $rule->url, '['.$rule->dag_id.'] '.$rule->name, $flag_reason['reason'], $flag_reason['user'], $flag_reason['date'] + ) + )); + } + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 25efaeb7..412edb46 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -28,7 +28,7 @@ class EventServiceProvider extends ServiceProvider 'App\\Events\\Rules\\Flagged' => [ 'App\\Listeners\\Rules\\RebuildRuleCache', - 'App\\Listeners\\Rules\\NotifyRuleEditors', + 'App\\Listeners\\Rules\\NotifyRuleContributor', 'App\\Listeners\\Rules\\NotifyTeamOwner', ], diff --git a/app/Services/MySgs/Api/EloquentHelpers/JobClientAccountMatcher.php b/app/Services/MySgs/Api/EloquentHelpers/JobClientAccountMatcher.php index 636d54b9..2dce2943 100644 --- a/app/Services/MySgs/Api/EloquentHelpers/JobClientAccountMatcher.php +++ b/app/Services/MySgs/Api/EloquentHelpers/JobClientAccountMatcher.php @@ -62,7 +62,7 @@ public function handle() } catch (\Exception $e) { logger($e->getMessage()); $job_metadata->client_found = false; - $job_metadata->client = ['name' => $customer_name]; + $job_metadata->client = ['name' => $customer_name ?? '[Unset]']; } diff --git a/composer.json b/composer.json index 8dd07254..d7c860ca 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ "softcreatr/jsonpath": "^0.5 || ^0.7", "spatie/laravel-model-states": "^2.1", "staudenmeir/eloquent-has-many-deep": "^1.7", + "thomasjohnkane/snooze": "^1.0", "tightenco/ziggy": "^0.9.4", "wehaa/custom-links": "^0.1.1" }, diff --git a/composer.lock b/composer.lock index f0b86314..b74ea284 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "93f072f7a158b57b22ab75e49859c0e1", + "content-hash": "6ec067964d9fcec1e4146e2787d3808a", "packages": [ { "name": "altek/accountant", @@ -12219,6 +12219,65 @@ ], "time": "2021-07-27T01:56:02+00:00" }, + { + "name": "thomasjohnkane/snooze", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/thomasjohnkane/snooze.git", + "reference": "b938359560f66d1be63b00d318b05d41e1e13928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thomasjohnkane/snooze/zipball/b938359560f66d1be63b00d318b05d41e1e13928", + "reference": "b938359560f66d1be63b00d318b05d41e1e13928", + "shasum": "" + }, + "require": { + "illuminate/support": "~6.0 || ~7.0 || ~8.0", + "php": ">=7.2.5" + }, + "require-dev": { + "orchestra/testbench": "~4.0|| ~5.0 || ~6.0", + "phpunit/phpunit": "^9.0", + "timacdonald/log-fake": "^1.7" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Thomasjohnkane\\Snooze\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Thomasjohnkane\\Snooze\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thomas Kane", + "email": "thomasjohnkane@gmail.com" + } + ], + "description": "Schedule future notifications and reminders in Laravel", + "keywords": [ + "delayed notifications", + "notifications", + "scheduled", + "snooze" + ], + "support": { + "issues": "https://github.com/thomasjohnkane/snooze/issues", + "source": "https://github.com/thomasjohnkane/snooze/tree/1.0.6" + }, + "time": "2021-06-28T00:49:35+00:00" + }, { "name": "tightenco/ziggy", "version": "0.9.4", diff --git a/config/snooze.php b/config/snooze.php new file mode 100644 index 00000000..f3e8d177 --- /dev/null +++ b/config/snooze.php @@ -0,0 +1,45 @@ + 'scheduled_notifications', + + /* + * The ScheduledNotification model to use. + * If you need to customise the model you can override this + */ + 'model' => ScheduledNotification::class, + + /* + * The frequency at which to send notifications + * + * Available options are everyMinute, everyFiveMinutes, everyTenMinutes, + * everyFifteenMinutes, everyThirtyMinutes, hourly, and daily. + */ + 'sendFrequency' => env('SCHEDULED_NOTIFICATION_SEND_FREQUENCY', 'everyMinute'), + + /* + * The tolerance at which to look for old notifications waiting to be sent, in seconds. + * This is to prevent sending a large amount of notifications if the command stops + * running. By default it's set to 24 hours + */ + 'sendTolerance' => env('SCHEDULED_NOTIFICATION_SEND_TOLERANCE', 60 * 60 * 24), + + /* + * The age at which to prune sent/cancelled notifications, in days. + * If set to null, pruning will be turned off. By default it's turned off + */ + 'pruneAge' => env('SCHEDULED_NOTIFICATION_PRUNE_AGE', null), + + /* + * Disable sending of scheduled notifications + * You will still be able to schedule notifications, + * and they will be sent once the scheduler is enabled. + */ + + 'disabled' => env('SCHEDULED_NOTIFICATIONS_DISABLED', false), +];