Skip to content
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
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,28 @@ That's all the setup required. The floodgate will now buffer notifications withi

### Sending a Summary

Implement `toSummary` on your notification to define what the summary looks like. It receives all buffered notification instances and should return an array shaped like your `toArray` method:
Implement `toSummary` on your notification to define what the summary looks like. It receives all buffered notification instances and should return a `Summary` value object:

```php
public function toSummary(array $items): array
use TestMonitor\Floodgate\Notifications\Summary;

public function toSummary(array $items): Summary
{
return [
'message' => ':count issues have been assigned to you',
'url' => route('issues.index'),
'icon' => 'exclamation-circle',
'properties' => ['count' => count($items)],
];
return (new Summary)
->title('Issue Activity')
->message(':count issues have been assigned to you')
->with(['count' => count($items)])
->subject('Your issue activity summary')
->action('View Issues', route('issues.index'));
}
```

`title`, `subject` and `action` are all optional:

- `title`, when set, is rendered as a heading with `message` as regular text below it; otherwise only `message` is rendered.
- `subject`, when omitted, falls back to "You have new notifications".
- `action`, when omitted, renders no button.

When a summary is sent, the `toArray` method on each individual notification is passed to the summary mail view as `$items`, allowing you to include per-item detail alongside the grouped summary.

### Customizing the Buffer Window
Expand Down Expand Up @@ -164,20 +172,23 @@ For full control, replace the summary class in the configuration:
'summary' => App\Notifications\IssueSummaryNotification::class,
```

Your custom class receives `$summary`, `$notifications`, and `$channels` in its constructor. Extend the default to override only what you need:
Your custom class receives a `Summary` value object, the buffered `$notifications`, and `$channels` in its constructor. Extend the default to override only what you need, for example the mail view:

```php
use Illuminate\Notifications\Messages\MailMessage;
use TestMonitor\Floodgate\Notifications\SummaryNotification;

class IssueSummaryNotification extends SummaryNotification
{
protected function subject(): string
public function toMail(mixed $notifiable): MailMessage
{
return 'Your issue activity summary';
return parent::toMail($notifiable)->cc('team@example.com');
}
}
```

To customize the subject on a per-notification basis, set it via the `subject` property when building the `Summary` in `toSummary()` instead (see [Sending a Summary](#sending-a-summary)).

## Tests

The package contains a full test suite. Run it using:
Expand Down
16 changes: 11 additions & 5 deletions resources/views/summary.blade.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
@component('mail::message')
# {{ __($summary['message'] ?? 'You have new notifications', $summary['properties'] ?? []) }}
@if ($summary->title)
# {{ __($summary->title, $summary->data) }}
@endif

{{ __($summary->message, $summary->data) }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehm... no...


@component('mail::table')
| # | Notification |
| # | @lang('Notification') |
| - | ----------- |
@foreach ($items as $index => $item)
| {{ $index + 1 }} | [{{ __($item['message'] ?? '', $item['properties'] ?? []) }}]({{ $item['url'] ?? '#' }}) |
| {{ $index + 1 }} | [{{ __($item['message'] ?? '', $item['data'] ?? []) }}]({{ $item['url'] ?? '#' }}) |
@endforeach
@endcomponent

@component('mail::button', ['url' => $summary['url'] ?? '/'])
View
@if ($summary->actionText)
@component('mail::button', ['url' => $summary->actionUrl])
@lang($summary->actionText)
Comment on lines +16 to +18

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mimicking Laravel :)

@endcomponent
@endif

@endcomponent
4 changes: 3 additions & 1 deletion src/Concerns/Gateable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace TestMonitor\Floodgate\Concerns;

use TestMonitor\Floodgate\Notifications\Summary;

trait Gateable
{
protected bool $floodgateExempt = false;
Expand All @@ -27,5 +29,5 @@ public function isFloodgateExempt(): bool
/*
* Return a grouped summary of multiple buffered notifications of this type.
*/
abstract public function toSummary(array $items): array;
abstract public function toSummary(array $items): Summary;
}
4 changes: 3 additions & 1 deletion src/Contracts/Gated.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace TestMonitor\Floodgate\Contracts;

use TestMonitor\Floodgate\Notifications\Summary;

interface Gated
{
/*
Expand All @@ -17,5 +19,5 @@ public function isFloodgateExempt(): bool;
/*
* Return a grouped summary of multiple buffered notifications of this type.
*/
public function toSummary(array $items): array;
public function toSummary(array $items): Summary;
}
109 changes: 109 additions & 0 deletions src/Notifications/Summary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace TestMonitor\Floodgate\Notifications;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Traits\Conditionable;

class Summary implements Arrayable
{
use Conditionable;

/**
* The title displayed above the summary message.
*/
public ?string $title = null;

/**
* The summary message, using the same :placeholder syntax as toArray().
*/
public string $message = '';

/**
* The replacement values for the message's :placeholders.
*/
public array $data = [];

/**
* The mail subject.
*/
public ?string $subject = null;

/**
* The text for the summary mail's call-to-action button.
*/
public ?string $actionText = null;

/**
* The URL for the summary mail's call-to-action button.
*/
public ?string $actionUrl = null;

/*
* Set the title displayed above the summary message.
*/
public function title(string $title): static
{
$this->title = $title;

return $this;
}

/*
* Set the summary message, using the same :placeholder syntax as toArray().
*/
public function message(string $message): static
{
$this->message = $message;

return $this;
}

/*
* Merge additional replacement values for the message's :placeholders.
*/
public function with(array|string $key, mixed $value = null): static
{
if (is_array($key)) {
$this->data = array_merge($this->data, $key);
} else {
$this->data[$key] = $value;
}

return $this;
}

/*
* Set the mail subject.
*/
public function subject(string $subject): static
{
$this->subject = $subject;

return $this;
}

/*
* Set the call-to-action button's text and URL.
*/
public function action(string $text, string $url): static
{
$this->actionText = $text;
$this->actionUrl = $url;

return $this;
}

/*
* Return the summary as an array, suitable for the database channel.
*/
public function toArray(): array
{
return array_filter([
'title' => $this->title,
'message' => $this->message,
'data' => $this->data,
'url' => $this->actionUrl,
], fn ($value) => ! is_null($value));
}
}
6 changes: 3 additions & 3 deletions src/Notifications/SummaryNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class SummaryNotification extends Notification
{
public function __construct(
protected array $summary,
protected Summary $summary,
protected array $notifications,
protected array $channels,
) {}
Expand All @@ -26,7 +26,7 @@ public function via(mixed $notifiable): array
*/
public function toArray(mixed $notifiable): array
{
return $this->summary;
return $this->summary->toArray();
}

/*
Expand All @@ -40,7 +40,7 @@ public function toMail(mixed $notifiable): MailMessage
);

return (new MailMessage)
->subject('You have new notifications')
->subject($this->summary->subject ?? __('You have new notifications'))
->markdown('floodgate::summary', [
'summary' => $this->summary,
'items' => $items,
Expand Down
2 changes: 1 addition & 1 deletion tests/FlushingNotificationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function it_sends_a_summary_when_multiple_notifications_were_buffered():
Notification::assertSentTo($user, SummaryNotification::class, function ($notification) use ($user) {
return $notification->toArray($user) === [
'message' => ':count test notifications',
'properties' => ['count' => 3],
'data' => ['count' => 3],
];
});
Notification::assertNotSentTo($user, TestNotification::class);
Expand Down
10 changes: 5 additions & 5 deletions tests/Notifications/TestNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Notifications\Notification;
use TestMonitor\Floodgate\Concerns\Gateable;
use TestMonitor\Floodgate\Middleware\ThrottlesNotifications;
use TestMonitor\Floodgate\Notifications\Summary;

class TestNotification extends Notification implements ShouldQueue
{
Expand Down Expand Up @@ -35,11 +36,10 @@ public function toArray(mixed $notifiable): array
return ['message' => 'Test notification'];
}

public function toSummary(array $items): array
public function toSummary(array $items): Summary
{
return [
'message' => ':count test notifications',
'properties' => ['count' => count($items)],
];
return (new Summary)
->message(':count test notifications')
->with(['count' => count($items)]);
}
}
Loading