179 lines
5.6 KiB
PHP
179 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Widgets;
|
|
|
|
use App\Models\Event;
|
|
use App\Models\EventJoinTokenEvent;
|
|
use Carbon\CarbonPeriod;
|
|
use Filament\Widgets\ChartWidget;
|
|
use Filament\Widgets\Concerns\InteractsWithPageFilters;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class JoinTokenTrendWidget extends ChartWidget
|
|
{
|
|
use InteractsWithPageFilters;
|
|
|
|
protected static ?int $sort = 2;
|
|
|
|
protected int|string|array $columnSpan = 'full';
|
|
|
|
protected function getData(): array
|
|
{
|
|
$filters = $this->resolveFilters();
|
|
$events = $this->baseQuery($filters)->get(['event_type', 'occurred_at']);
|
|
$hourly = $filters['start']->diffInHours($filters['end']) <= 48;
|
|
$bucketFormat = $hourly ? 'Y-m-d H:00' : 'Y-m-d';
|
|
$labelFormat = $hourly ? 'M d H:00' : 'M d';
|
|
|
|
$periodStart = $hourly ? $filters['start']->copy()->startOfHour() : $filters['start']->copy()->startOfDay();
|
|
$period = CarbonPeriod::create($periodStart, $hourly ? '1 hour' : '1 day', $filters['end']);
|
|
|
|
$grouped = $events->groupBy(fn (EventJoinTokenEvent $event) => $event->occurred_at?->format($bucketFormat));
|
|
|
|
$labels = [];
|
|
$success = [];
|
|
$failures = [];
|
|
$rateLimited = [];
|
|
$uploads = [];
|
|
|
|
foreach ($period as $point) {
|
|
$key = $point->format($bucketFormat);
|
|
$bucket = $grouped->get($key, collect());
|
|
|
|
$labels[] = $point->translatedFormat($labelFormat);
|
|
$success[] = $bucket->whereIn('event_type', self::SUCCESS_EVENTS)->count();
|
|
$failures[] = $bucket->whereIn('event_type', self::FAILURE_EVENTS)->count();
|
|
$rateLimited[] = $bucket->whereIn('event_type', self::RATE_LIMIT_EVENTS)->count();
|
|
$uploads[] = $bucket->whereIn('event_type', self::UPLOAD_EVENTS)->count();
|
|
}
|
|
|
|
return [
|
|
'datasets' => [
|
|
[
|
|
'label' => __('admin.join_token_analytics.trend.success'),
|
|
'data' => $success,
|
|
'borderColor' => '#16a34a',
|
|
'backgroundColor' => 'rgba(22, 163, 74, 0.2)',
|
|
'tension' => 0.35,
|
|
'fill' => false,
|
|
],
|
|
[
|
|
'label' => __('admin.join_token_analytics.trend.failures'),
|
|
'data' => $failures,
|
|
'borderColor' => '#dc2626',
|
|
'backgroundColor' => 'rgba(220, 38, 38, 0.2)',
|
|
'tension' => 0.35,
|
|
'fill' => false,
|
|
],
|
|
[
|
|
'label' => __('admin.join_token_analytics.trend.rate_limited'),
|
|
'data' => $rateLimited,
|
|
'borderColor' => '#f59e0b',
|
|
'backgroundColor' => 'rgba(245, 158, 11, 0.2)',
|
|
'tension' => 0.35,
|
|
'fill' => false,
|
|
],
|
|
[
|
|
'label' => __('admin.join_token_analytics.trend.uploads'),
|
|
'data' => $uploads,
|
|
'borderColor' => '#2563eb',
|
|
'backgroundColor' => 'rgba(37, 99, 235, 0.2)',
|
|
'tension' => 0.35,
|
|
'fill' => false,
|
|
],
|
|
],
|
|
'labels' => $labels,
|
|
];
|
|
}
|
|
|
|
protected function getType(): string
|
|
{
|
|
return 'line';
|
|
}
|
|
|
|
public function getHeading(): ?string
|
|
{
|
|
return __('admin.join_token_analytics.trend.heading');
|
|
}
|
|
|
|
private function baseQuery(array $filters): Builder
|
|
{
|
|
$query = EventJoinTokenEvent::query()
|
|
->whereBetween('occurred_at', [$filters['start'], $filters['end']]);
|
|
|
|
if ($filters['event_id']) {
|
|
$query->where('event_id', $filters['event_id']);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
private function resolveFilters(): array
|
|
{
|
|
$eventId = $this->pageFilters['event_id'] ?? null;
|
|
$eventId = is_numeric($eventId) ? (int) $eventId : null;
|
|
$range = is_string($this->pageFilters['range'] ?? null) ? $this->pageFilters['range'] : '24h';
|
|
|
|
[$start, $end] = $this->resolveWindow($range, $eventId);
|
|
|
|
return [
|
|
'event_id' => $eventId,
|
|
'range' => $range,
|
|
'start' => $start,
|
|
'end' => $end,
|
|
];
|
|
}
|
|
|
|
private function resolveWindow(string $range, ?int $eventId): array
|
|
{
|
|
$now = now();
|
|
$start = match ($range) {
|
|
'2h' => $now->copy()->subHours(2),
|
|
'6h' => $now->copy()->subHours(6),
|
|
'12h' => $now->copy()->subHours(12),
|
|
'7d' => $now->copy()->subDays(7),
|
|
default => $now->copy()->subHours(24),
|
|
};
|
|
$end = $now;
|
|
|
|
if ($range === 'event_day' && $eventId) {
|
|
$eventDate = Event::query()->whereKey($eventId)->value('date');
|
|
|
|
if ($eventDate) {
|
|
$eventDay = Carbon::parse($eventDate);
|
|
$start = $eventDay->copy()->startOfDay();
|
|
$end = $eventDay->copy()->endOfDay();
|
|
}
|
|
}
|
|
|
|
return [$start, $end];
|
|
}
|
|
|
|
private const SUCCESS_EVENTS = [
|
|
'access_granted',
|
|
'gallery_access_granted',
|
|
];
|
|
|
|
private const RATE_LIMIT_EVENTS = [
|
|
'token_rate_limited',
|
|
'access_rate_limited',
|
|
'download_rate_limited',
|
|
];
|
|
|
|
private const FAILURE_EVENTS = [
|
|
'invalid_token',
|
|
'token_expired',
|
|
'token_revoked',
|
|
'event_not_public',
|
|
'gallery_expired',
|
|
'token_rate_limited',
|
|
'access_rate_limited',
|
|
'download_rate_limited',
|
|
];
|
|
|
|
private const UPLOAD_EVENTS = [
|
|
'upload_completed',
|
|
];
|
|
}
|