Add join token analytics dashboard and align Filament views
This commit is contained in:
147
app/Filament/Widgets/JoinTokenOverviewWidget.php
Normal file
147
app/Filament/Widgets/JoinTokenOverviewWidget.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user