I'm working on a Laravel/Filament application where I need to broadcast events from a queued job to update a Filament RelationManager's state.
Setup
I have a queued job that generates an analysis for a patient. When the analysis is complete, it broadcasts an event that should trigger a refresh in my Filament RelationManager component.
The broadcasting works (I receive the "Analysis Complete" notification when the job is done), but the Livewire listener in my RelationManager isn't triggering.
What's Working
What's Not Working
Logs
[2024-12-04 15:07:27] local.INFO: Dispatching job with: {"livewire_record_type":"App\\Models\\Patient","livewire_record_data":{"id":1...}}
[2024-12-04 15:07:27] local.INFO: Job constructor called with: {"patient_type":"App\\Models\\Patient","patient_data":{"id":1...}}
[2024-12-04 15:07:28] local.INFO: Broadcasting analysis event {"channel":"analyses","event":"analysis-completed"}
[2024-12-04 15:07:29] local.INFO: Broadcasting analysis completed event
Code Structure
Job (app/Jobs/GeneratePatientAnalysis.php):
class GeneratePatientAnalysis implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected int $userId;
public function __construct(protected Patient $patient)
{
\Log::info('Job constructor called with:', [
'patient_type' => get_class($patient),
'patient_data' => $patient->toArray()
]);
$this->userId = auth()->id();
}
public function handle(AiAnalysisService $aiService): void
{
$latestVersion = $this->patient->analyses()
->latest()
->first()?->version ?? '0.0';
$newVersion = number_format((float) $latestVersion + 0.1, 1);
$analysis = Analysis::create([
'patient_id' => $this->patient->id,
'generated_by' => $this->userId,
'status' => 'pending',
'version' => $newVersion,
'model_used' => 'gpt-4o',
]);
try {
$patientData = $this->gatherPatientData();
$analysis->update([
'analysis_content' => 'Generated Analysis Here',
'status' => 'completed',
'completed_at' => now(),
]);
$user = User::find($this->userId);
Notification::make()
->title('Analysis Complete')
->success()
->broadcast([$user])
->send();
\Log::info('Broadcasting analysis event', [
'channel' => 'analyses',
'event' => 'analysis-completed'
]);
broadcast(new AnalysisCompleted($analysis))->toOthers();
} catch (\Exception $e) {
$analysis->update(['status' => 'failed']);
Notification::make()
->title('Analysis Failed')
->danger()
->send();
}
}
}
Event (app/Events/AnalysisCompleted.php):
class AnalysisCompleted implements ShouldBroadcast
{
public Analysis $analysis;
public function broadcastOn(): array
{
return ['analyses'];
}
public function broadcastAs(): string
{
return 'analysis-completed';
}
}
RelationManager (app/Filament/Resources/PatientResource/RelationManagers/AnalysesRelationManager.php):
protected $listeners = [
'echo:analyses,analysis-completed' => 'refreshAnalyses'
];
public function refreshAnalyses(): void
{
$this->isGenerating = false;
$this->render();
}
public function table(Table $table): Table
{
return $table
->columns([]),
->headerActions([
\Filament\Tables\Actions\Action::make('generate')
->label('Generate New Analysis')
->action(function ($livewire) {
\Log::info('Dispatching job with:', [
'livewire_record_type' => get_class($livewire->ownerRecord),
'livewire_record_data' => $livewire->ownerRecord->toArray()
]);
$this->isGenerating = true;
GeneratePatientAnalysis::dispatch($livewire->ownerRecord)
->onQueue('default');
$this->render();
Notification::make()
->title('Analysis Generation Started')
->body('You will be notified when the analysis is ready.')
->success()
->send();
})
->icon('heroicon-o-plus')
->disabled(fn() => $this->isGenerating)
])
}
I fixed the issue and it is as silly as you might imagine. I was missing the prefixed dot on the event name.
protected $listeners = [
'echo:analyses,.analysis-completed' => 'refreshAnalyses'
];
Now everything works as expected.