can('panel.access') ?? false) && ContentView::query()->since(self::DAYS)->exists(); } /** * @return array */ protected function getStats(): array { return collect($this->top()) ->map(fn (array $row) => Stat::make($row['title'], (string) $row['count']) ->description(__('panel.views.in_days', ['days' => self::DAYS])) ->descriptionIcon('heroicon-m-eye') ->color('info')) ->all(); } /** * The three most-read records of any kind, resolved to their titles. * * Cached for a few minutes: this is drawn on the dashboard, which is the * first page everybody lands on, and the answer does not change minute to * minute — the underlying figures are daily totals. * * @return array */ protected function top(): array { return Cache::remember('views:most-read', now()->addMinutes(5), function (): array { $rows = ContentView::query() ->since(self::DAYS) ->selectRaw('viewable_type, viewable_id, SUM(count) as total') ->groupBy('viewable_type', 'viewable_id') ->orderByDesc('total') ->limit(3) ->get(); // Loaded per group rather than with the relation, because these // are three rows of different types and eager loading a morph for // three records buys nothing. return $rows ->map(function (ContentView $row): ?array { $record = $row->viewable; return $record instanceof Model ? ['title' => ContentTypes::title($record), 'count' => (int) $row->total] : null; }) ->filter() ->values() ->all(); }); } }