Modify import completed notification (#19714)

* add code

* add for exports, clean up and docs

---------

Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
Tom Janssen
2026-04-20 14:26:50 +02:00
committed by GitHub
parent a4f07a54c4
commit 6bc46098af
8 changed files with 228 additions and 14 deletions
+43
View File
@@ -693,6 +693,49 @@ ImportAction::make()
<UtilityInjection set="actions" version="4.x">As well as allowing a static value, the `headerOffset()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
## Customizing the completion notification
When an import finishes, Filament sends a notification to the user who started it. You can customize the title and body of that notification by overriding `getCompletedNotificationTitle()` and `getCompletedNotificationBody()` on your importer:
```php
use Filament\Actions\Imports\Models\Import;
public static function getCompletedNotificationTitle(Import $import): string
{
return 'Your product import has finished';
}
public static function getCompletedNotificationBody(Import $import): string
{
return $import->successful_rows . ' products were imported.';
}
```
For anything beyond the title and body — for example, changing the notification color, adding extra actions, or replacing the icon — override `modifyCompletedNotification()`. You can either mutate the `Notification` passed in and return it, or build and return a completely new one:
```php
use Filament\Actions\Action;
use Filament\Actions\Imports\Models\Import;
use Filament\Notifications\Notification;
public static function modifyCompletedNotification(Notification $notification, Import $import): Notification
{
$notification->icon('heroicon-o-shopping-bag');
if ($import->getOptions()['sendWelcomeEmails'] ?? false) {
$notification->actions([
...$notification->getActions(),
Action::make('viewWelcomeEmails')
->url(route('emails.sent')),
]);
}
return $notification;
}
```
The `Import` model exposes the column mapping and options the user selected via `$import->getColumnMap()` and `$import->getOptions()`, so you can tailor the notification based on what the user imported.
## Customizing the import job
The default job for processing imports is `Filament\Actions\Imports\Jobs\ImportCsv`. If you want to extend this class and override any of its methods, you may replace the original class in the `register()` method of a service provider:
+43
View File
@@ -753,6 +753,49 @@ public function configureXlsxWriterBeforeClose(Writer $writer): Writer
}
```
## Customizing the completion notification
When an export finishes, Filament sends a notification to the user who started it. You can customize the title and body of that notification by overriding `getCompletedNotificationTitle()` and `getCompletedNotificationBody()` on your exporter:
```php
use Filament\Actions\Exports\Models\Export;
public static function getCompletedNotificationTitle(Export $export): string
{
return 'Your product export is ready';
}
public static function getCompletedNotificationBody(Export $export): string
{
return $export->successful_rows . ' products were exported.';
}
```
For anything beyond the title and body — for example, changing the notification color, adding extra actions, or replacing the icon — override `modifyCompletedNotification()`. You can either mutate the `Notification` passed in and return it, or build and return a completely new one:
```php
use Filament\Actions\Action;
use Filament\Actions\Exports\Models\Export;
use Filament\Notifications\Notification;
public static function modifyCompletedNotification(Notification $notification, Export $export): Notification
{
$notification->icon('heroicon-o-shopping-bag');
if ($export->getOptions()['notifyTeam'] ?? false) {
$notification->actions([
...$notification->getActions(),
Action::make('shareWithTeam')
->url(route('exports.share', $export)),
]);
}
return $notification;
}
```
The `Export` model exposes the column mapping and options the user selected via `$export->getColumnMap()` and `$export->getOptions()`, so you can tailor the notification based on what the user exported.
## Customizing the export job
The default job for processing exports is `Filament\Actions\Exports\Jobs\PrepareCsvExport`. If you want to extend this class and override any of its methods, you may replace the original class in the `register()` method of a service provider:
@@ -8,6 +8,7 @@ use Filament\Actions\ActionGroup;
use Filament\Actions\Exports\Enums\Contracts\ExportFormat as ExportFormatInterface;
use Filament\Actions\Exports\Enums\ExportFormat;
use Filament\Actions\Exports\Models\Export;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Component;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
@@ -96,6 +97,11 @@ abstract class Exporter
return __('filament-actions::export.notifications.completed.title');
}
public static function modifyCompletedNotification(Notification $notification, Export $export): Notification
{
return $notification;
}
/**
* @return array<int, object>
*/
@@ -51,9 +51,14 @@ class ExportCompletion implements ShouldQueue
return;
}
$this->export->columnMap($this->columnMap);
$this->export->options($this->options);
$failedRowsCount = $this->export->getFailedRowsCount();
Notification::make()
$isSynchronous = ($this->connection === 'sync') || (blank($this->connection) && (config('queue.default') === 'sync'));
$notification = Notification::make()
->title($this->exporter::getCompletedNotificationTitle($this->export))
->body($this->exporter::getCompletedNotificationBody($this->export))
->when(
@@ -76,12 +81,16 @@ class ExportCompletion implements ShouldQueue
)),
)
->when(
($this->connection === 'sync') ||
(blank($this->connection) && (config('queue.default') === 'sync')),
fn (Notification $notification) => $notification
->persistent()
->send(),
fn (Notification $notification) => $notification->sendToDatabase($this->export->user, isEventDispatched: true),
$isSynchronous,
fn (Notification $notification) => $notification->persistent(),
);
$notification = $this->exporter::modifyCompletedNotification($notification, $this->export);
if ($isSynchronous) {
$notification->send();
} else {
$notification->sendToDatabase($this->export->user, isEventDispatched: true);
}
}
}
@@ -26,6 +26,16 @@ class Export extends Model
{
use Prunable;
/**
* @var array<string, string>
*/
protected array $columnMap = [];
/**
* @var array<string, mixed>
*/
protected array $options = [];
/**
* @return array<string, string>
*/
@@ -75,6 +85,9 @@ class Export extends Model
array $columnMap,
array $options,
): Exporter {
$this->columnMap($columnMap);
$this->options($options);
return app($this->exporter, [
'export' => $this,
'columnMap' => $columnMap,
@@ -82,6 +95,42 @@ class Export extends Model
]);
}
/**
* @param array<string, string> $columnMap
*/
public function columnMap(array $columnMap): static
{
$this->columnMap = $columnMap;
return $this;
}
/**
* @return array<string, string>
*/
public function getColumnMap(): array
{
return $this->columnMap;
}
/**
* @param array<string, mixed> $options
*/
public function options(array $options): static
{
$this->options = $options;
return $this;
}
/**
* @return array<string, mixed>
*/
public function getOptions(): array
{
return $this->options;
}
public function getFailedRowsCount(): int
{
return $this->total_rows - $this->successful_rows;
+16 -7
View File
@@ -284,9 +284,14 @@ class ImportAction extends Action
return;
}
$import->columnMap($columnMap);
$import->options($options);
$failedRowsCount = $import->getFailedRowsCount();
Notification::make()
$isSynchronous = ($jobConnection === 'sync') || (blank($jobConnection) && (config('queue.default') === 'sync'));
$notification = Notification::make()
->title($import->importer::getCompletedNotificationTitle($import))
->body($import->importer::getCompletedNotificationBody($import))
->when(
@@ -314,13 +319,17 @@ class ImportAction extends Action
]),
)
->when(
($jobConnection === 'sync') ||
(blank($jobConnection) && (config('queue.default') === 'sync')),
fn (Notification $notification) => $notification
->persistent()
->send(),
fn (Notification $notification) => $notification->sendToDatabase($import->user, isEventDispatched: true),
$isSynchronous,
fn (Notification $notification) => $notification->persistent(),
);
$notification = $import->importer::modifyCompletedNotification($notification, $import);
if ($isSynchronous) {
$notification->send();
} else {
$notification->sendToDatabase($import->user, isEventDispatched: true);
}
})
->dispatch();
@@ -6,6 +6,7 @@ use Carbon\CarbonInterface;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\Imports\Models\Import;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Component;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Queue\Middleware\WithoutOverlapping;
@@ -313,6 +314,11 @@ abstract class Importer
return __('filament-actions::import.notifications.completed.title');
}
public static function modifyCompletedNotification(Notification $notification, Import $import): Notification
{
return $notification;
}
/**
* @return array<int, object>
*/
@@ -27,6 +27,16 @@ class Import extends Model
{
use Prunable;
/**
* @var array<string, string>
*/
protected array $columnMap = [];
/**
* @var array<string, mixed>
*/
protected array $options = [];
/**
* @return array<string, string>
*/
@@ -81,6 +91,9 @@ class Import extends Model
array $columnMap,
array $options,
): Importer {
$this->columnMap($columnMap);
$this->options($options);
return app($this->importer, [
'import' => $this,
'columnMap' => $columnMap,
@@ -88,6 +101,42 @@ class Import extends Model
]);
}
/**
* @param array<string, string> $columnMap
*/
public function columnMap(array $columnMap): static
{
$this->columnMap = $columnMap;
return $this;
}
/**
* @return array<string, string>
*/
public function getColumnMap(): array
{
return $this->columnMap;
}
/**
* @param array<string, mixed> $options
*/
public function options(array $options): static
{
$this->options = $options;
return $this;
}
/**
* @return array<string, mixed>
*/
public function getOptions(): array
{
return $this->options;
}
public function getFailedRowsCount(): int
{
return $this->total_rows - $this->successful_rows;