feat: Add API endpoints for staff online status and queue monitoring.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Laravel\Horizon\Contracts\JobRepository;
|
||||
use Laravel\Horizon\Contracts\MetricsRepository;
|
||||
use Laravel\Horizon\Contracts\WorkloadRepository;
|
||||
|
||||
class QueueMonitorController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly JobRepository $jobs,
|
||||
private readonly MetricsRepository $metrics,
|
||||
private readonly WorkloadRepository $workload,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* GET /api/queue/jobs/recent
|
||||
* Recent processed jobs tracked by Horizon.
|
||||
*/
|
||||
public function recent(): JsonResponse
|
||||
{
|
||||
$jobs = $this->jobs->getRecent();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'total' => $this->jobs->countRecent(),
|
||||
'data' => $this->formatJobs($jobs, 'completed'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/queue/jobs/pending
|
||||
* Jobs currently waiting in the queue (per-queue workload + Horizon pending list).
|
||||
*/
|
||||
public function pending(): JsonResponse
|
||||
{
|
||||
$jobs = $this->jobs->getPending();
|
||||
$queues = $this->workload->get();
|
||||
|
||||
$workload = collect($queues)->map(fn (array $q) => [
|
||||
'queue' => $q['name'],
|
||||
'length' => $q['length'],
|
||||
'wait' => $q['wait'],
|
||||
'processes' => $q['processes'],
|
||||
])->values();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'total' => $this->jobs->countPending(),
|
||||
'workload' => $workload,
|
||||
'data' => $this->formatJobs($jobs, 'pending'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/queue/jobs/failed
|
||||
* Failed jobs tracked by Horizon.
|
||||
*/
|
||||
public function failed(): JsonResponse
|
||||
{
|
||||
$jobs = $this->jobs->getFailed();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'total' => $this->jobs->countFailed(),
|
||||
'data' => $this->formatJobs($jobs, 'failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/queue/jobs/metrics
|
||||
* Queue throughput, runtime, and per-queue/per-job breakdown.
|
||||
*/
|
||||
public function metrics(): JsonResponse
|
||||
{
|
||||
$measuredQueues = $this->metrics->measuredQueues();
|
||||
$measuredJobs = $this->metrics->measuredJobs();
|
||||
|
||||
$queueMetrics = collect($measuredQueues)->map(fn (string $queue) => [
|
||||
'queue' => $queue,
|
||||
'throughput' => $this->metrics->throughputForQueue($queue),
|
||||
'runtime_ms' => round($this->metrics->runtimeForQueue($queue), 2),
|
||||
])->values();
|
||||
|
||||
$jobMetrics = collect($measuredJobs)->map(fn (string $job) => [
|
||||
'job' => $job,
|
||||
'throughput' => $this->metrics->throughputForJob($job),
|
||||
'runtime_ms' => round($this->metrics->runtimeForJob($job), 2),
|
||||
])->values();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'throughput' => $this->metrics->throughput(),
|
||||
'jobs_processed_per_minute' => $this->metrics->jobsProcessedPerMinute(),
|
||||
'counts' => [
|
||||
'recent' => $this->jobs->countRecent(),
|
||||
'pending' => $this->jobs->countPending(),
|
||||
'failed' => $this->jobs->countFailed(),
|
||||
'completed' => $this->jobs->countCompleted(),
|
||||
'recently_failed' => $this->jobs->countRecentlyFailed(),
|
||||
],
|
||||
'queues' => $queueMetrics,
|
||||
'jobs' => $jobMetrics,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a collection of Horizon job objects into a consistent array shape.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $jobs
|
||||
* @param string $defaultStatus
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
private function formatJobs(\Illuminate\Support\Collection $jobs, string $defaultStatus): \Illuminate\Support\Collection
|
||||
{
|
||||
return $jobs->map(function (object $job) use ($defaultStatus) {
|
||||
$payload = isset($job->payload) ? json_decode($job->payload, true) : [];
|
||||
|
||||
return [
|
||||
'id' => $job->id ?? null,
|
||||
'name' => $payload['displayName'] ?? $job->name ?? null,
|
||||
'queue' => $job->queue ?? null,
|
||||
'status' => $job->status ?? $defaultStatus,
|
||||
'attempts' => $job->attempts ?? 0,
|
||||
'started_at' => $this->formatTimestamp($job->reserved_at ?? null),
|
||||
'completed_at' => $this->formatTimestamp($job->completed_at ?? null),
|
||||
'failed_at' => $this->formatTimestamp($job->failed_at ?? null),
|
||||
'exception' => $job->exception ?? null,
|
||||
];
|
||||
})->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Unix timestamp to ISO-8601 string, or return null.
|
||||
*/
|
||||
private function formatTimestamp(int|float|string|null $ts): ?string
|
||||
{
|
||||
if ($ts === null || $ts === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \Carbon\Carbon::createFromTimestamp((int) $ts)->toIso8601String();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user