Skip to content

Commit

Permalink
feat: prune sent/cancelled notifications (#37)
Browse files Browse the repository at this point in the history
* feat: prune sent/cancelled notifications

* docs: update readme + typos

* fix: turn off by default
  • Loading branch information
atymic authored Dec 16, 2019
1 parent 92426f4 commit 0f3914b
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 2 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,17 @@ The only thing you need to do is make sure `schedule:run` is also running. You c
If your scheduler stops working, a backlog of scheduled notifications will build up. To prevent users receiving all of
the old scheduled notifications at once, the command will only send mail within the configured tolerance.
By default this is set to 24 hours, so only mail scheduled to be sent within that window will be sent. This can be
configured in the `snooze.php` config file.
configured (in seconds) using the `SCHEDULED_NOTIFICATION_SEND_TOLERANCE` environment variable or in the `snooze.php` config file.

### Setting the prune age

The package can prune sent and cancelled messages that were sent/cancelled more than x days ago. You can
configure this using the `SCHEDULED_NOTIFICATION_PRUNE_AGE` environment variable or in the `snooze.php` config file
(unit is days). This feature is turned off by default.

#### Detailed Examples

- [Delayed Notifcation (1 week)][3]
- [Delayed Notification (1 week)][3]
- [Simple On-boarding Email Drip][5]
- [Exposing Custom Data to the Notification/Email][4]

Expand Down
6 changes: 6 additions & 0 deletions config/snooze.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@
* 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),
];
59 changes: 59 additions & 0 deletions src/Console/Commands/PruneScheduledNotifications.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Thomasjohnkane\Snooze\Console\Commands;

use Carbon\Carbon;
use Illuminate\Console\Command;
use Thomasjohnkane\Snooze\Models\ScheduledNotification;

class PruneScheduledNotifications extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snooze:prune';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Prune scheduled notifications that have been sent or cancelled';

/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
$pruneDays = config('snooze.pruneAge');

if ($pruneDays === null) {
$this->error('Pruning of scheduled notifications is disabled');

return;
}

$pruneBeforeDate = Carbon::now()->subDays($pruneDays);

$notifications = ScheduledNotification::where(function ($query) {
$query->where('sent_at', '!=', null);
$query->orWhere('cancelled_at', '!=', null);
})->where(function ($query) use ($pruneBeforeDate) {
$query->where('sent_at', '<=', $pruneBeforeDate);
$query->orWhere('cancelled_at', '<=', $pruneBeforeDate);
});

$totalDeleted = 0;

do {
$deleted = $notifications->take(1000)->delete();
$totalDeleted += $deleted;
} while ($deleted !== 0);

$this->info(sprintf('Pruned %d scheduled notifications', $totalDeleted));
}
}
5 changes: 5 additions & 0 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider

protected $commands = [
Console\Commands\SendScheduledNotifications::class,
Console\Commands\PruneScheduledNotifications::class,
];

public function boot()
Expand All @@ -19,6 +20,10 @@ public function boot()
$frequency = config('snooze.sendFrequency', 'everyMinute');
$schedule = $this->app->make(Schedule::class);
$schedule->command('snooze:send')->{$frequency}();

if (config('snooze.pruneAge') !== null) {
$schedule->command('snooze:prune')->daily();
}
});

$this->publishes([
Expand Down
69 changes: 69 additions & 0 deletions tests/PruneCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Thomasjohnkane\Snooze\Tests;

use Carbon\Carbon;
use Thomasjohnkane\Snooze\Models\ScheduledNotification as ScheduledNotificationModel;
use Thomasjohnkane\Snooze\Tests\Models\User;
use Thomasjohnkane\Snooze\Tests\Notifications\TestNotification;

class PruneCommandTest extends TestCase
{
public function testItCanBeDisabled()
{
$this->artisan('snooze:prune')
->expectsOutput('Pruning of scheduled notifications is disabled');
}

public function testItDoesNotPruneRecentNotifications()
{
$this->app->config->set('snooze.pruneAge', 30);

$target = User::find(1);

$target->notifyAt(new TestNotification(User::find(2)), Carbon::now());
$target->notifyAt(new TestNotification(User::find(2)), Carbon::now());
$target->notifyAt(new TestNotification(User::find(2)), Carbon::now());

$this->artisan('snooze:prune')
->expectsOutput('Pruned 0 scheduled notifications')
->assertExitCode(0);
}

public function testPrunesCorrectNotifications()
{
$this->app->config->set('snooze.pruneAge', 30);

$target = User::find(1);

$notification = $target->notifyAt(new TestNotification(User::find(2)), Carbon::now());
$base = ScheduledNotificationModel::find($notification->getId());

$sent2MonthsAgo = $base->replicate();
$sent2MonthsAgo->sent_at = Carbon::now()->subMonths(2);
$sent2MonthsAgo->save();

$cancelled2MonthsAgo = $base->replicate();
$cancelled2MonthsAgo->cancelled_at = Carbon::now()->subMonths(2);
$cancelled2MonthsAgo->save();

$sent1WeekAgo = $base->replicate();
$sent1WeekAgo->sent_at = Carbon::now()->subWeek();
$sent1WeekAgo->save();

$cancelled1WeekAgo = $base->replicate();
$cancelled1WeekAgo->cancelled_at = Carbon::now()->subWeek();
$cancelled1WeekAgo->save();

$this->artisan('snooze:prune')
->expectsOutput('Pruned 2 scheduled notifications')
->assertExitCode(0);

$this->assertDatabaseMissing('scheduled_notifications', ['id' => $sent2MonthsAgo->id]);
$this->assertDatabaseMissing('scheduled_notifications', ['id' => $cancelled2MonthsAgo->id]);

$this->assertDatabaseHas('scheduled_notifications', ['id' => $base->id]);
$this->assertDatabaseHas('scheduled_notifications', ['id' => $sent1WeekAgo->id]);
$this->assertDatabaseHas('scheduled_notifications', ['id' => $cancelled1WeekAgo->id]);
}
}

0 comments on commit 0f3914b

Please sign in to comment.