1<?php2 3use Livewire\Volt\Volt;4 5Volt::route('/job-batching', 'job-batching');
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\Attributes\Title; 11use Livewire\Volt\Component; 12use Livewire\WithPagination; 13 14new 15#[Title('Job Batching - Larva Interactions')] 16class extends Component 17{ 18 use WithPagination; 19 20 public $perPage = 10; 21 public $search = ''; 22 public $batchId; 23 public $batchFinished, $batchCancelled = false; 24 public $batchProgress = 0; 25 public $startBatch = false; 26 private $table = 'websites'; 27 28 public function updated($property) 29 { 30 if ($property === 'search') { 31 $this->resetPage(); 32 } 33 } 34 35 #[Computed] 36 public function websites() 37 { 38 $website = DB::table($this->table); 39 40 if (!empty($this->search)) { 41 $search = trim(strtolower($this->search)); 42 $website = $website->where('Domain', 'like', '%'.$search.'%'); 43 } 44 45 $website = $website->orderBy('GlobalRank')->cursorPaginate($this->perPage); 46 47 return $website; 48 } 49 50 public function start() 51 { 52 $this->startBatch = true; 53 54 DB::table($this->table)->truncate(); 55 56 $websites = LazyCollection::make(function () { 57 $handle = fopen(base_path('csvfile/majestic_million.csv'), 'r'); 58 59 $header = fgetcsv($handle); 60 while (($line = fgetcsv($handle)) !== false) { 61 yield array_combine($header, $line); 62 } 63 }); 64 65 $batch = Bus::batch([])->dispatch(); 66 $websites->chunk(1000)->each(function ($chunk) use ($batch) { 67 $batch->add(new ProcessInsertRecord('websites', $chunk->toArray())); 68 }); 69 70 $this->batchId = $batch->id; 71 } 72 73 #[Computed] 74 public function batch() 75 { 76 if (!$this->batchId) { 77 return null; 78 } 79 80 return Bus::findBatch($this->batchId); 81 } 82 83 public function updateBatchProgress() 84 { 85 $this->batchProgress = $this->batch->progress(); 86 $this->batchFinished = $this->batch->finished(); 87 $this->batchCancelled = $this->batch->cancelled(); 88 89 if ($this->batchFinished) { 90 $this->startBatch = false; 91 } 92 } 93 94 public function with(): array 95 { 96 return [ 97 'md_content' => markdown_convert(resource_path('docs/job-batching.md')) 98 ]; 99 }100}101?>102 103<div x-data="{ batchFinished: $wire.entangle('batchFinished').live,104 batchCancelled: $wire.entangle('batchCancelled').live,105 batchProgress: $wire.entangle('batchProgress').live,106 startBatch: $wire.entangle('startBatch').live,107 }" x-effect="console.log('batch finished: ', batchFinished, 'batch cancelled: ', batchCancelled, 'batch progress: ', batchProgress, 'start batch: ', startBatch)"108 x-init="$watch('batchFinished', (value) => {109 if (value) {110 startBatch = false;111 }112 });">113 114 <a href="/" class="underline text-blue-500">Back</a>115 116 <h1 class="text-2xl">Job Batching</h1>117 {!! $md_content !!}118 <p>This example shows how to implements Job Batching with Livewire and Alpine using <a class="underline text-blue-500" href="https://blog.majestic.com/development/majestic-million-csv-daily/">Majestic Million data.</a></p>119 <p>The CSV file stored in <code class="bg-gray-100 p-1 inline-block rounded">csvfile</code> directory.</p>120 121 <div class="my-4">122 <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>123 124 <template x-if="startBatch && !batchFinished">125 <div class="relative pt-1" wire:key="batch-start" wire:poll="updateBatchProgress">126 <div class="overflow-hidden h-4 flex rounded bg-green-100">127 <div :style="{width: batchProgress + '%'}" class="bg-green-500 transition-all"></div>128 </div>129 <div class="flex justify-end" x-text="batchProgress + '%'"></div>130 </div>131 </template>132 133 <template x-if="batchFinished">134 <div class="relative pt-1" wire:key="batch-end">135 <div class="overflow-hidden h-4 flex rounded bg-green-100">136 <div :style="{width: '100%'}" class="bg-green-500 transition-all"></div>137 </div>138 <div class="flex justify-end" x-text="'100%'"></div>139 </div>140 </template>141 142 <template x-if="batchCancelled && !batchFinished">143 <div class="mt-4 flex justify-end">144 <p class="text-red-600">Failed!</p>145 </div>146 </template>147 </div>148 149 <div class="flex items-center gap-4 my-4">150 <div class="flex-1">151 <label>152 <select wire:model.change="perPage">153 <option>10</option>154 <option>25</option>155 <option>50</option>156 <option>100</option>157 </select>158 entries per page159 </label>160 <input type="text" wire:model.change="search">161 </div>162 163 <button wire:click="$refresh" class="rounded p-2 bg-blue-500 text-white">Refresh</button>164 </div>165 166 <table class="w-full table-auto border-collapse border border-slate-400">167 <thead>168 <tr>169 <th class="p-2 border border-slate-300">GlobalRank</th>170 <th class="p-2 border border-slate-300">Domain</th>171 <th class="p-2 border border-slate-300">TLD</th>172 <th class="p-2 border border-slate-300">RefSubNets</th>173 <th class="p-2 border border-slate-300">RefIPs</th>174 <th class="p-2 border border-slate-300">PrevGlobalRank</th>175 <th class="border border-slate-300">PrevRefSubNets</th>176 <th class="border border-slate-300">PrevRefIPs</th>177 </tr>178 </thead>179 <tbody>180 @forelse ($this->websites as $site)181 <tr @class(['bg-gray-100'=> ($loop->index % 2 === 0)])>182 <td class="p-2 border border-slate-300">{{ $site->GlobalRank }}</td>183 <td class="p-2 border border-slate-300">{{ $site->Domain }}</td>184 <td class="p-2 border border-slate-300">{{ $site->TLD }}</td>185 <td class="p-2 border border-slate-300">{{ $site->RefSubNets }}</td>186 <td class="p-2 border border-slate-300">{{ $site->RefIPs }}</td>187 <td class="p-2 border border-slate-300">{{ $site->PrevGlobalRank }}</td>188 <td class="p-2 border border-slate-300">{{ $site->PrevRefSubNets }}</td>189 <td class="p-2 border border-slate-300">{{ $site->PrevRefIPs }}</td>190 </tr>191 @empty192 <tr>193 <td class="p-2 text-center bg-gray-100" colspan="12">No data.</td>194 </tr>195 @endforelse196 </tbody>197 <tfoot>198 <tr>199 <td colspan="12">{{ $this->websites->links() }}</td>200 </tr>201 </tfoot>202 </table>203</div>
This example shows how to implements Job Batching with Livewire and Alpine using Majestic Million data.
The CSV file stored in csvfile
directory.
Failed!
GlobalRank | Domain | TLD | RefSubNets | RefIPs | PrevGlobalRank | PrevRefSubNets | PrevRefIPs | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
No data. | |||||||||||