Search code examples
laravellaravel-livewirebroadcastlaravel-filamentlaravel-reverb

Laravel Broadcasting Events from Queue Job Not Triggering Filament Livewire Listener


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

  • The job executes successfully
  • The event is broadcast (confirmed via logs)
  • Filament notifications are received through the same broadcasting system

What's Not Working

  • The Livewire listener in the RelationManager never triggers The
  • button state remains disabled after job completion
  • The table doesn't refresh with new data

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)
            ])
  }

Solution

  • 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.