feat(failed-jobs): add database-agnostic failed jobs with bulk actions
- Create custom implementation to replace vendor plugin - Add database-agnostic JSON queries (MySQL, MariaDB, PG, SQLite, SQLSRV) - Implement Retry/Prune header actions with queue selection - Maintain all original features: filters, search, actions, bulk operations BREAKING CHANGE: New route /failed-jobs/compatible-failed-jobs Fixes MariaDB JSON syntax errors
This commit is contained in:
132
app/Filament/Resources/FailedJobs/CompatibleFailedJobsTable.php
Normal file
132
app/Filament/Resources/FailedJobs/CompatibleFailedJobsTable.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\FailedJobs;
|
||||
|
||||
use BinaryBuilds\FilamentFailedJobs\Actions\DeleteJobAction;
|
||||
use BinaryBuilds\FilamentFailedJobs\Actions\DeleteJobsBulkAction;
|
||||
use BinaryBuilds\FilamentFailedJobs\Actions\RetryJobAction;
|
||||
use BinaryBuilds\FilamentFailedJobs\Actions\RetryJobsBulkAction;
|
||||
use BinaryBuilds\FilamentFailedJobs\Models\FailedJob;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\Filter;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CompatibleFailedJobsTable
|
||||
{
|
||||
/**
|
||||
* Configure the table with database-agnostic JSON queries
|
||||
*/
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns(array_filter([
|
||||
TextColumn::make('id')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('connection')->searchable(),
|
||||
|
||||
TextColumn::make('queue')->searchable(),
|
||||
|
||||
TextColumn::make('payload')->label('Job')
|
||||
->formatStateUsing(function ($state) {
|
||||
return json_decode($state, true)['displayName'];
|
||||
})->searchable(),
|
||||
|
||||
TextColumn::make('exception')->wrap()->limit(100),
|
||||
|
||||
TextColumn::make('failed_at')->searchable(),
|
||||
]))
|
||||
->filters(self::getCompatibleFiltersForIndex())
|
||||
->recordActions([
|
||||
RetryJobAction::make()->iconButton()->tooltip(__('Retry Job')),
|
||||
ViewAction::make()->iconButton()->tooltip(__('View Job')),
|
||||
DeleteJobAction::make()->iconButton()->tooltip(__('Delete Job')),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
RetryJobsBulkAction::make(),
|
||||
DeleteJobsBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Database-agnostic version of getFiltersForIndex
|
||||
*/
|
||||
private static function getCompatibleFiltersForIndex(): array
|
||||
{
|
||||
$jobs = FailedJob::query()
|
||||
->select(['connection', 'queue'])
|
||||
->selectRaw(self::getJsonExtractExpression('displayName', 'job'))
|
||||
->get();
|
||||
|
||||
$connections = $jobs->pluck('connection', 'connection')->map(fn ($conn) => ucfirst($conn))->toArray();
|
||||
$queues = $jobs->pluck('queue', 'queue')->map(fn ($queue) => ucfirst($queue))->toArray();
|
||||
$jobNames = $jobs->pluck('job', 'job')->toArray();
|
||||
|
||||
return [
|
||||
SelectFilter::make('Connection')->options($connections),
|
||||
SelectFilter::make('Queue')->options($queues),
|
||||
Filter::make('Job')
|
||||
->schema([
|
||||
Select::make('job')->options($jobNames),
|
||||
])
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
return $query
|
||||
->when(
|
||||
$data['job'],
|
||||
fn (Builder $query, $job): Builder => $query->whereRaw(self::getJsonExtractExpression('displayName').' = ?', [$job]),
|
||||
);
|
||||
}),
|
||||
Filter::make('failed_at')
|
||||
->schema([
|
||||
DatePicker::make('failed_at'),
|
||||
])
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
return $query
|
||||
->when(
|
||||
$data['failed_at'],
|
||||
fn (Builder $query, $date): Builder => $query->whereDate('failed_at', '>=', $date),
|
||||
);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database-agnostic JSON extraction expression
|
||||
*/
|
||||
private static function getJsonExtractExpression(string $path, ?string $alias = null): string
|
||||
{
|
||||
$driver = DB::getDriverName();
|
||||
|
||||
switch ($driver) {
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
$expression = "JSON_UNQUOTE(JSON_EXTRACT(payload, '$.".$path."'))";
|
||||
break;
|
||||
case 'pgsql':
|
||||
$expression = "payload->>'".$path."'";
|
||||
break;
|
||||
case 'sqlite':
|
||||
$expression = "json_extract(payload, '$.".$path."')";
|
||||
break;
|
||||
case 'sqlsrv':
|
||||
$expression = "JSON_VALUE(payload, '$.".$path."')";
|
||||
break;
|
||||
default:
|
||||
// Fallback to MySQL syntax
|
||||
$expression = "JSON_UNQUOTE(JSON_EXTRACT(payload, '$.".$path."'))";
|
||||
break;
|
||||
}
|
||||
|
||||
return $alias ? $expression.' AS '.$alias : $expression;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user