148 lines
4.6 KiB
PHP
148 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Widgets;
|
|
|
|
use App\Models\Event;
|
|
use App\Models\EventJoinTokenEvent;
|
|
use Filament\Widgets\Concerns\InteractsWithPageFilters;
|
|
use Filament\Widgets\StatsOverviewWidget;
|
|
use Filament\Widgets\StatsOverviewWidget\Stat;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class JoinTokenOverviewWidget extends StatsOverviewWidget
|
|
{
|
|
use InteractsWithPageFilters;
|
|
|
|
protected static ?int $sort = 1;
|
|
|
|
protected int|string|array $columnSpan = 'full';
|
|
|
|
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',
|
|
];
|
|
|
|
protected function getStats(): array
|
|
{
|
|
$filters = $this->resolveFilters();
|
|
$totals = $this->totalsByEventType($filters);
|
|
|
|
$success = $this->sumTotals($totals, self::SUCCESS_EVENTS);
|
|
$failures = $this->sumTotals($totals, self::FAILURE_EVENTS);
|
|
$rateLimited = $this->sumTotals($totals, self::RATE_LIMIT_EVENTS);
|
|
$uploads = $this->sumTotals($totals, self::UPLOAD_EVENTS);
|
|
|
|
$ratio = $success > 0 ? round(($failures / $success) * 100, 1) : null;
|
|
$ratioLabel = $ratio !== null ? "{$ratio}%" : __('admin.join_token_analytics.stats.no_data');
|
|
|
|
return [
|
|
Stat::make(__('admin.join_token_analytics.stats.success'), number_format($success))
|
|
->color('success'),
|
|
Stat::make(__('admin.join_token_analytics.stats.failures'), number_format($failures))
|
|
->color($failures > 0 ? 'danger' : 'success'),
|
|
Stat::make(__('admin.join_token_analytics.stats.rate_limited'), number_format($rateLimited))
|
|
->color($rateLimited > 0 ? 'warning' : 'success'),
|
|
Stat::make(__('admin.join_token_analytics.stats.uploads'), number_format($uploads))
|
|
->color('primary'),
|
|
Stat::make(__('admin.join_token_analytics.stats.failure_ratio'), $ratioLabel)
|
|
->color($ratio !== null && $ratio >= 50 ? 'danger' : 'warning'),
|
|
];
|
|
}
|
|
|
|
private function totalsByEventType(array $filters): array
|
|
{
|
|
return $this->baseQuery($filters)
|
|
->selectRaw('event_type, COUNT(*) as total')
|
|
->groupBy('event_type')
|
|
->get()
|
|
->mapWithKeys(fn (EventJoinTokenEvent $event) => [$event->event_type => (int) $event->total])
|
|
->all();
|
|
}
|
|
|
|
private function sumTotals(array $totals, array $types): int
|
|
{
|
|
$sum = 0;
|
|
|
|
foreach ($types as $type) {
|
|
$sum += (int) ($totals[$type] ?? 0);
|
|
}
|
|
|
|
return $sum;
|
|
}
|
|
|
|
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];
|
|
}
|
|
}
|