1<?php2 3use App\Livewire\JobBatching;4 5Route::get('/job-batching', JobBatching::class);
1<?php 2 3namespace App\Livewire; 4 5use App\Jobs\ProcessInsertRecord; 6use Illuminate\Support\Facades\Bus; 7use Illuminate\Support\Facades\DB; 8use Illuminate\Support\LazyCollection; 9use Livewire\Attributes\Computed;10use Livewire\Component;11use Livewire\WithPagination;12 13class JobBatching extends Component14{15 use WithPagination;16 17 public $perPage = 10;18 public $search = '';19 public $batchId;20 public $batchFinished, $batchCancelled = false;21 public $batchProgress = 0;22 public $startBatch = false;23 private $table = 'websites';24 25 public function updated($property)26 {27 if ($property === 'search') {28 $this->resetPage();29 }30 }31 32 #[Computed]33 public function websites()34 {35 $website = DB::table($this->table);36 37 if (!empty($this->search)) {38 $search = trim(strtolower($this->search));39 $website = $website->where('Domain', 'like', '%'.$search.'%');40 }41 42 $website = $website->orderBy('GlobalRank')->cursorPaginate($this->perPage);43 44 return $website;45 }46 47 public function start()48 {49 $this->startBatch = true;50 51 DB::table($this->table)->truncate();52 53 $websites = LazyCollection::make(function () {54 $handle = fopen(base_path('csvfile/majestic_million.csv'), 'r');55 56 $header = fgetcsv($handle);57 while (($line = fgetcsv($handle)) !== false) {58 yield array_combine($header, $line);59 }60 });61 62 $batch = Bus::batch([])->dispatch();63 $websites->chunk(1000)->each(function ($chunk) use ($batch) {64 $batch->add(new ProcessInsertRecord('websites', $chunk->toArray()));65 });66 67 $this->batchId = $batch->id;68 }69 70 #[Computed]71 public function batch()72 {73 if (!$this->batchId) {74 return null;75 }76 77 return Bus::findBatch($this->batchId);78 }79 80 public function updateBatchProgress()81 {82 $this->batchProgress = $this->batch->progress();83 $this->batchFinished = $this->batch->finished();84 $this->batchCancelled = $this->batch->cancelled();85 86 if ($this->batchFinished) {87 $this->startBatch = false;88 }89 }90 91 public function render()92 {93 return view('livewire.job-batching')94 ->title('Job Batching');95 }96}
1<div x-data="{ batchFinished: $wire.entangle('batchFinished').live, 2 batchCancelled: $wire.entangle('batchCancelled').live, 3 batchProgress: $wire.entangle('batchProgress').live, 4 startBatch: $wire.entangle('startBatch').live, 5 }" x-effect="console.log('batch finished: ', batchFinished, 'batch cancelled: ', batchCancelled, 'batch progress: ', batchProgress, 'start batch: ', startBatch)" 6 x-init="$watch('batchFinished', (value) => { 7 if (value) { 8 startBatch = false; 9 } 10 });"> 11 12 <a href="/" class="underline text-blue-500">Back</a> 13 14 <h1 class="text-2xl">Job Batching</h1> 15 16 <p>This example shows how to implements Job Batching with Livewire and AlpineJS using <a class="underline text-blue-500" href="https://blog.majestic.com/development/majestic-million-csv-daily/">Majestic Million data.</a></p> 17 <p>The CSV file stored in <code class="bg-gray-100 p-1 inline-block rounded">csvfile</code> directory.</p> 18 19 <div class="my-4"> 20 <button type="button" @click="startBatch = true; batchFinished = false; batchCancelled = false; batchProgress = 0; $wire.start();" :disabled="startBatch" :class="startBatch ? 'disabled:bg-slate-100' : ''" class="bg-blue-300 p-2 rounded">Import</button> 21 22 <template x-if="startBatch && !batchFinished"> 23 <div class="relative pt-1" wire:key="batch-start" wire:poll="updateBatchProgress"> 24 <div class="overflow-hidden h-4 flex rounded bg-green-100"> 25 <div :style="{width: batchProgress + '%'}" class="bg-green-500 transition-all"></div> 26 </div> 27 <div class="flex justify-end" x-text="batchProgress + '%'"></div> 28 </div> 29 </template> 30 31 <template x-if="batchFinished"> 32 <div class="relative pt-1" wire:key="batch-end"> 33 <div class="overflow-hidden h-4 flex rounded bg-green-100"> 34 <div :style="{width: '100%'}" class="bg-green-500 transition-all"></div> 35 </div> 36 <div class="flex justify-end" x-text="'100%'"></div> 37 </div> 38 </template> 39 40 <template x-if="batchCancelled && !batchFinished"> 41 <div class="mt-4 flex justify-end"> 42 <p class="text-red-600">Failed!</p> 43 </div> 44 </template> 45 </div> 46 47 <div class="flex items-center gap-4 my-4"> 48 <div class="flex-1"> 49 <label> 50 <select wire:model.change="perPage"> 51 <option>10</option> 52 <option>25</option> 53 <option>50</option> 54 <option>100</option> 55 </select> 56 entries per page 57 </label> 58 <input type="text" wire:model.change="search"> 59 </div> 60 61 <button wire:click="$refresh" class="rounded p-2 bg-blue-500 text-white">Refresh</button> 62 </div> 63 64 <table class="w-full table-auto border-collapse border border-slate-400"> 65 <thead> 66 <tr> 67 <th class="p-2 border border-slate-300">GlobalRank</th> 68 <th class="p-2 border border-slate-300">Domain</th> 69 <th class="p-2 border border-slate-300">TLD</th> 70 <th class="p-2 border border-slate-300">RefSubNets</th> 71 <th class="p-2 border border-slate-300">RefIPs</th> 72 <th class="p-2 border border-slate-300">PrevGlobalRank</th> 73 <th class="border border-slate-300">PrevRefSubNets</th> 74 <th class="border border-slate-300">PrevRefIPs</th> 75 </tr> 76 </thead> 77 <tbody> 78 @forelse ($this->websites as $site) 79 <tr @class(['bg-gray-100'=> ($loop->index % 2 === 0)])> 80 <td class="p-2 border border-slate-300">{{ $site->GlobalRank }}</td> 81 <td class="p-2 border border-slate-300">{{ $site->Domain }}</td> 82 <td class="p-2 border border-slate-300">{{ $site->TLD }}</td> 83 <td class="p-2 border border-slate-300">{{ $site->RefSubNets }}</td> 84 <td class="p-2 border border-slate-300">{{ $site->RefIPs }}</td> 85 <td class="p-2 border border-slate-300">{{ $site->PrevGlobalRank }}</td> 86 <td class="p-2 border border-slate-300">{{ $site->PrevRefSubNets }}</td> 87 <td class="p-2 border border-slate-300">{{ $site->PrevRefIPs }}</td> 88 </tr> 89 @empty 90 <tr> 91 <td class="p-2 text-center bg-gray-100" colspan="12">No data.</td> 92 </tr> 93 @endforelse 94 </tbody> 95 <tfoot> 96 <tr> 97 <td colspan="12">{{ $this->websites->links() }}</td> 98 </tr> 99 </tfoot>100 </table>101</div>
This example shows how to implements Job Batching with Livewire and AlpineJS using Majestic Million data.
The CSV file stored in csvfile
directory.
Failed!
GlobalRank | Domain | TLD | RefSubNets | RefIPs | PrevGlobalRank | PrevRefSubNets | PrevRefIPs | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
No data. | |||||||||||