Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hours precision when email is dispatched #2576

Merged
merged 4 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ jobs:
composer update --${{ matrix.stability }} --prefer-dist --no-interaction

- name: Execute tests
run: ./vendor/bin/testbench package:test --parallel --no-coverage
run: ./vendor/bin/testbench package:test
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ The `after` constraint accept a `CarbonInterface` as well. The difference, is th
->after($order->created_at)
```

### Precision
Hour Precision

The `precision` method provides fine-grained control over when emails are sent using MailatorSchedule. It allows you to specify specific hours or intervals within a 24-hour period. Here's an example of how to use the precision method:
```php
->many()
->precision([3-4])
```
This will schedule the email dispatch between '03:00:00' AM and '04:59:59' AM.

or
```php
->once()
->precision([1])
```
This will schedule the email dispatch between '01:00:00' AM and '01:59:59'.

You can continue this pattern to specify the desired hour(s) within the range of 1 to 24.

**Important: When using the precision feature in the Mailator scheduler, it is recommended to set the scheduler to run at intervals that are less than an hour. You can choose intervals such as every 5 minutes, 10 minutes, 30 minutes, or any other desired duration.**
### Constraint

The `constraint()` method accept an instance of `Binarcode\LaravelMailator\Constraints\SendScheduleConstraint`. Each constraint will be called when the scheduler will try to send the email. If all constraints return true, the email will be sent.
Expand Down
2 changes: 1 addition & 1 deletion database/migrations/create_mailator_tables.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class CreateMailatorTables extends Migration
$table->json('recipients')->nullable();
$table->text('when')->nullable();
$table->string('frequency_option')->default(MailatorSchedule::FREQUENCY_OPTIONS_ONCE)->comment('How often send email notification.');

$table->json('schedule_at_hours')->nullable();
$table->timestamp('last_sent_at')->nullable();
$table->timestamp('last_failed_at')->nullable();
$table->timestamp('completed_at')->nullable();
Expand Down
2 changes: 1 addition & 1 deletion src/Actions/RunSchedulersAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class RunSchedulersAction
{
use ClassResolver;

public function __invoke()
public function __invoke(): void
{
static::scheduler()::query()
->ready()
Expand Down
18 changes: 18 additions & 0 deletions src/Constraints/HoursSchedulerCheckerConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Binarcode\LaravelMailator\Constraints;

use Binarcode\LaravelMailator\Models\MailatorSchedule;
use Illuminate\Support\Collection;

class HoursSchedulerCheckerConstraint implements SendScheduleConstraint
{
public function canSend(MailatorSchedule $schedule, Collection $logs): bool
{
if (! $schedule->hasPrecision()) {
return true;
}

return in_array(now()->hour, $schedule->schedule_at_hours);
}
}
7 changes: 6 additions & 1 deletion src/Models/Concerns/ConstraintsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Binarcode\LaravelMailator\Constraints\BeforeConstraint;
use Binarcode\LaravelMailator\Constraints\DailyConstraint;
use Binarcode\LaravelMailator\Constraints\Descriptionable;
use Binarcode\LaravelMailator\Constraints\HoursSchedulerCheckerConstraint;
use Binarcode\LaravelMailator\Constraints\ManualConstraint;
use Binarcode\LaravelMailator\Constraints\ManyConstraint;
use Binarcode\LaravelMailator\Constraints\NeverConstraint;
Expand Down Expand Up @@ -34,6 +35,7 @@ public function configurationsPasses(): bool
ManyConstraint::class,
DailyConstraint::class,
WeeklyConstraint::class,
HoursSchedulerCheckerConstraint::class,
])
->map(fn ($class) => app($class))
->every(fn (SendScheduleConstraint $event) => $event->canSend($this, $this->logs));
Expand All @@ -49,7 +51,10 @@ public function eventsPasses(): bool
return collect($this->constraints)
->map(fn (string $event) => unserialize($event))
->filter(fn ($event) => is_subclass_of($event, SendScheduleConstraint::class))
->filter(fn (SendScheduleConstraint $event) => $event->canSend($this, $this->logs))->count() === collect($this->constraints)->count();
->filter(fn (SendScheduleConstraint $event) => $event->canSend(
$this,
$this->logs
))->count() === collect($this->constraints)->count();
}

public function constraintsDescriptions(): array
Expand Down
22 changes: 18 additions & 4 deletions src/Models/MailatorSchedule.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
* @property Carbon $last_failed_at
* @property string $failure_reason
* @property Carbon $last_sent_at
* @property array|null $schedule_at_hours
* @property Carbon $completed_at
* @property string $frequency_option
* @property-read Collection $logs
Expand Down Expand Up @@ -88,6 +89,7 @@ public function getTable()
protected $casts = [
'constraints' => 'array',
'recipients' => 'array',
'schedule_at_hours' => 'array',
'timestamp_target' => 'datetime',
'last_failed_at' => 'datetime',
'last_sent_at' => 'datetime',
Expand Down Expand Up @@ -244,6 +246,11 @@ public function isWeekly(): bool
return $this->frequency_option === static::FREQUENCY_OPTIONS_WEEKLY;
}

public function hasPrecision(): bool
{
return (bool) $this->schedule_at_hours;
}

public function isAfter(): bool
{
return $this->time_frame_origin === static::TIME_FRAME_ORIGIN_AFTER;
Expand Down Expand Up @@ -307,6 +314,13 @@ public function days(int $number): self
return $this;
}

public function precision(array $scheduleAtHours): self
{
$this->schedule_at_hours = $scheduleAtHours;

return $this;
}

public function weeks(int $number): static
{
$this->delay_minutes = $number * ConverterEnum::MINUTES_IN_WEEK;
Expand Down Expand Up @@ -368,7 +382,7 @@ public function shouldSend(): bool
}

return true;
} catch (Exception | Throwable $e) {
} catch (Exception|Throwable $e) {
$this->markAsFailed($e->getMessage());

app(ResolveGarbageAction::class)->handle($this);
Expand Down Expand Up @@ -408,7 +422,7 @@ public function execute(bool $now = false): void
dispatch(new SendMailJob($this));
}
}
} catch (Exception | Throwable $e) {
} catch (Exception|Throwable $e) {
$this->markAsFailed($e->getMessage());
}
}
Expand All @@ -427,7 +441,7 @@ public function getMailable(): ?Mailable
{
try {
return unserialize($this->mailable_class);
} catch (Throwable | TypeError $e) {
} catch (Throwable|TypeError $e) {
$this->markAsFailed($e->getMessage());
}

Expand Down Expand Up @@ -494,7 +508,7 @@ public function actionClass(Action $action): self
return $this;
}

public function tag(string | array $tag): self
public function tag(string|array $tag): self
{
if (is_array($tag)) {
$tag = implode(',', $tag);
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/Models/MailatorScheduleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function test_can_use_carbon_target_date_before(): void

// MailatorSchedule::run();
// Mail::assertNothingSent();

$this->travelTo(now()->addDays(6));
MailatorSchedule::run();

Expand Down
79 changes: 79 additions & 0 deletions tests/Feature/WithWeekendsConstraintTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Binarcode\LaravelMailator\Tests\Feature;

use Binarcode\LaravelMailator\Models\MailatorSchedule;
use Binarcode\LaravelMailator\Tests\Fixtures\InvoiceReminderMailable;
use Binarcode\LaravelMailator\Tests\TestCase;
use Carbon\Carbon;
use Illuminate\Support\Facades\Mail;
use Spatie\TestTime\TestTime;

class WithWeekendsConstraintTest extends TestCase
{
public function test_can_send_mail_with_precision_at_the_given_hour(): void
{
Mail::fake();
Mail::assertNothingSent();

MailatorSchedule::init('Invoice reminder.')
->recipients([
'zoo@bar.com',
])
->mailable(
(new InvoiceReminderMailable())->to('foo@bar.com')
)
->precision([5])
->save();

MailatorSchedule::run();
Mail::assertNotSent(InvoiceReminderMailable::class);

$this->travelTo(Carbon::parse('05:00:00'));
MailatorSchedule::run();

Mail::assertSent(InvoiceReminderMailable::class);

$this->travelTo(Carbon::parse('06:00:00'));

MailatorSchedule::run();

Mail::assertSent(InvoiceReminderMailable::class, 1);
}

public function test_can_set_precision_in_interval(): void
{
TestTime::freeze();

Mail::fake();
Mail::assertNothingSent();

MailatorSchedule::init('Invoice reminder.')
->recipients([
'zoo@bar.com',
])
->mailable(
(new InvoiceReminderMailable())->to('foo@bar.com')
)
->many()
->precision([1, 2])
->save();

$this->travelTo(Carbon::parse('12:00:00'));
MailatorSchedule::run();
Mail::assertNotSent(InvoiceReminderMailable::class);

$this->travelTo(Carbon::parse('01:00:00'));
MailatorSchedule::run();
Mail::assertSent(InvoiceReminderMailable::class);

$this->travelTo(Carbon::parse('02:59:59'));
MailatorSchedule::run();
Mail::assertSent(InvoiceReminderMailable::class);

$this->travelTo(Carbon::parse('03:00:00'));
MailatorSchedule::run();

Mail::assertSent(InvoiceReminderMailable::class, 2);
}
}
1 change: 0 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Illuminate\Contracts\View\Factory;
use Mockery as m;
use Orchestra\Testbench\TestCase as Orchestra;
use Swift_Mailer;

class TestCase extends Orchestra
{
Expand Down