<?php declare(strict_types=1); namespace Engelsystem\Controllers\Metrics; use Carbon\Carbon; use Engelsystem\Database\Database; use Engelsystem\Models\EventConfig; use Engelsystem\Models\Faq; use Engelsystem\Models\LogEntry; use Engelsystem\Models\Message; use Engelsystem\Models\News; use Engelsystem\Models\NewsComment; use Engelsystem\Models\OAuth; use Engelsystem\Models\Question; use Engelsystem\Models\Room; use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\User\License; use Engelsystem\Models\User\PasswordReset; use Engelsystem\Models\User\PersonalData; use Engelsystem\Models\User\Settings; use Engelsystem\Models\User\State; use Engelsystem\Models\User\User; use Engelsystem\Models\Worklog; use Illuminate\Contracts\Database\Query\Builder as BuilderContract; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Query\Expression as QueryExpression; use Illuminate\Support\Collection; class Stats { public function __construct(protected Database $db) { } /** * The number of not arrived users * * @param bool|null $working */ public function arrivedUsers(bool $working = null): int { $query = State::whereArrived(true); if (!is_null($working)) { $query ->leftJoin('worklogs', 'worklogs.user_id', '=', 'users_state.user_id') ->leftJoin('shift_entries', 'shift_entries.user_id', '=', 'users_state.user_id') ->distinct(); $query->where(function ($query) use ($working): void { /** @var QueryBuilder $query */ if ($working) { $query ->whereNotNull('shift_entries.shift_id') ->orWhereNotNull('worklogs.hours'); return; } $query ->whereNull('shift_entries.shift_id') ->whereNull('worklogs.hours'); }); } return $query->count('users_state.user_id'); } /** * The number of not arrived users */ public function newUsers(): int { return State::whereArrived(false)->count(); } public function forceActiveUsers(): int { return State::whereForceActive(true)->count(); } public function usersPronouns(): int { return PersonalData::where('pronoun', '!=', '')->count(); } public function email(string $type): int { return match ($type) { 'system' => Settings::whereEmailShiftinfo(true)->count(), 'humans' => Settings::whereEmailHuman(true)->count(), 'goody' => Settings::whereEmailGoody(true)->count(), 'news' => Settings::whereEmailNews(true)->count(), default => 0, }; } /** * The number of currently working users * * @param bool|null $freeloaded */ public function currentlyWorkingUsers(bool $freeloaded = null): int { $query = User::query() ->join('shift_entries', 'shift_entries.user_id', '=', 'users.id') ->join('shifts', 'shifts.id', '=', 'shift_entries.shift_id') ->where('shifts.start', '<=', Carbon::now()) ->where('shifts.end', '>', Carbon::now()); if (!is_null($freeloaded)) { $query->where('shift_entries.freeloaded', '=', $freeloaded); } return $query->count(); } protected function vouchersQuery(): Builder { return State::query(); } public function vouchers(): int { return (int) $this->vouchersQuery()->sum('got_voucher'); } public function vouchersBuckets(array $buckets): array { $return = []; foreach ($buckets as $bucket) { $query = $this->vouchersQuery(); if ($bucket !== '+Inf') { $query->where('got_voucher', '<=', $bucket); } $return[$bucket] = $query->count('got_voucher'); } return $return; } public function tshirts(): int { return State::whereGotShirt(true)->count(); } public function tshirtSizes(): Collection { return PersonalData::query() ->select(['shirt_size', $this->raw('COUNT(shirt_size) AS count')]) ->whereNotNull('shirt_size') ->groupBy(['shirt_size']) ->get(); } public function languages(): Collection { return Settings::query() ->select(['language', $this->raw('COUNT(language) AS count')]) ->groupBy(['language']) ->get(); } public function themes(): Collection { return Settings::query() ->select(['theme', $this->raw('COUNT(theme) AS count')]) ->groupBy(['theme']) ->get(); } /** * @param string|null $vehicle */ public function licenses(string $vehicle): int { $mapping = [ 'has_car' => 'has_car', 'forklift' => 'drive_forklift', 'car' => 'drive_car', '3.5t' => 'drive_3_5t', '7.5t' => 'drive_7_5t', '12t' => 'drive_12t', ]; $query = (new License()) ->getQuery() ->where($mapping[$vehicle], true); return $query->count(); } /** * @param bool|null $done * @param bool|null $freeloaded * * @codeCoverageIgnore because it is only used in functions that use TIMESTAMPDIFF */ protected function workSecondsQuery(bool $done = null, bool $freeloaded = null): QueryBuilder { $query = $this ->getQuery('shift_entries') ->join('shifts', 'shifts.id', '=', 'shift_entries.shift_id'); if (!is_null($freeloaded)) { $query->where('freeloaded', '=', $freeloaded); } if (!is_null($done)) { $query->where('end', ($done ? '<' : '>='), Carbon::now()); } return $query; } /** * The amount of worked seconds * * @param bool|null $done * @param bool|null $freeloaded * * @codeCoverageIgnore as TIMESTAMPDIFF is not implemented in SQLite */ public function workSeconds(bool $done = null, bool $freeloaded = null): int { $query = $this->workSecondsQuery($done, $freeloaded); return (int) $query->sum($this->raw('TIMESTAMPDIFF(MINUTE, start, end) * 60')); } /** * The number of worked shifts * * @param bool|null $done * @param bool|null $freeloaded * * @codeCoverageIgnore as TIMESTAMPDIFF is not implemented in SQLite */ public function workBuckets(array $buckets, bool $done = null, bool $freeloaded = null): array { return $this->getBuckets( $buckets, $this->workSecondsQuery($done, $freeloaded), 'user_id', 'SUM(TIMESTAMPDIFF(MINUTE, start, end) * 60)', 'SUM(TIMESTAMPDIFF(MINUTE, start, end) * 60)' ); } protected function getBuckets( array $buckets, BuilderContract $basicQuery, string $groupBy, string $having, string $count ): array { $return = []; foreach ($buckets as $bucket) { $query = clone $basicQuery; $query->groupBy($groupBy); if ($bucket !== '+Inf') { $query->having($this->raw($having), '<=', $bucket); } $return[$bucket] = count($query->get($this->raw($count))); } return $return; } public function worklogSeconds(): int { return (int) Worklog::query() ->sum($this->raw('hours * 60 * 60')); } public function worklogBuckets(array $buckets): array { return $this->getBuckets( $buckets, Worklog::query(), 'user_id', 'SUM(hours * 60 * 60)', 'SUM(hours * 60 * 60)' ); } public function rooms(): int { return Room::query() ->count(); } public function shifts(): int { return Shift::count(); } /** * @param bool|null $meeting */ public function announcements(bool $meeting = null): int { $query = is_null($meeting) ? News::query() : News::whereIsMeeting($meeting); return $query->count(); } public function comments(): int { return NewsComment::query() ->count(); } /** * @param bool|null $answered */ public function questions(bool $answered = null): int { $query = Question::query(); if (!is_null($answered)) { if ($answered) { $query->whereNotNull('answerer_id'); } else { $query->whereNull('answerer_id'); } } return $query->count(); } public function faq(): int { return Faq::query()->count(); } public function messages(): int { return Message::query()->count(); } public function sessions(): int { return $this ->getQuery('sessions') ->count(); } public function oauth(): Collection { return OAuth::query() ->select(['provider', $this->raw('COUNT(provider) AS count')]) ->groupBy(['provider']) ->get(); } public function databaseRead(): float { $start = microtime(true); (new EventConfig())->findOrNew('last_metrics'); return microtime(true) - $start; } public function databaseWrite(): float { $config = (new EventConfig())->findOrNew('last_metrics'); $config ->setAttribute('name', 'last_metrics') ->setAttribute('value', new Carbon()); $start = microtime(true); $config->save(); return microtime(true) - $start; } /** * @param string|null $level */ public function logEntries(string $level = null): int { $query = is_null($level) ? LogEntry::query() : LogEntry::whereLevel($level); return $query->count(); } public function passwordResets(): int { return PasswordReset::query()->count(); } protected function getQuery(string $table): QueryBuilder { return $this->db ->getConnection() ->table($table); } protected function raw(mixed $value): QueryExpression { return $this->db->getConnection()->raw($value); } }