diff --git a/src/Controllers/Metrics/Controller.php b/src/Controllers/Metrics/Controller.php index 01fe1d6a..0fd26396 100644 --- a/src/Controllers/Metrics/Controller.php +++ b/src/Controllers/Metrics/Controller.php @@ -7,6 +7,7 @@ use Engelsystem\Controllers\BaseController; use Engelsystem\Http\Exceptions\HttpForbidden; use Engelsystem\Http\Request; use Engelsystem\Http\Response; +use Psr\Log\LogLevel; class Controller extends BaseController { @@ -62,6 +63,15 @@ class Controller extends BaseController ['labels' => ['state' => 'arrived', 'working' => 'no'], 'value' => $this->stats->arrivedUsers(false)], ['labels' => ['state' => 'arrived', 'working' => 'yes'], 'value' => $this->stats->arrivedUsers(true)], ], + 'licenses' => [ + 'type' => 'gauge', + 'help' => 'The total number of licenses', + ['labels' => ['type' => 'forklift'], 'value' => $this->stats->licenses('forklift')], + ['labels' => ['type' => 'car'], 'value' => $this->stats->licenses('car')], + ['labels' => ['type' => '3.5t'], 'value' => $this->stats->licenses('3.5t')], + ['labels' => ['type' => '7.5t'], 'value' => $this->stats->licenses('7.5t')], + ['labels' => ['type' => '12.5t'], 'value' => $this->stats->licenses('12.5t')], + ], 'users_working' => [ 'type' => 'gauge', ['labels' => ['freeloader' => false], $this->stats->currentlyWorkingUsers(false)], @@ -73,7 +83,36 @@ class Controller extends BaseController ['labels' => ['state' => 'planned'], 'value' => $this->stats->workSeconds(false, false)], ['labels' => ['state' => 'freeloaded'], 'value' => $this->stats->workSeconds(null, true)], ], + 'worklog_seconds' => ['type' => 'gauge', $this->stats->worklogSeconds()], + 'shifts' => ['type' => 'gauge', $this->stats->shifts()], + 'announcements' => [ + 'type' => 'gauge', + ['labels' => ['type' => 'news'], 'value' => $this->stats->announcements(false)], + ['labels' => ['type' => 'meeting'], 'value' => $this->stats->announcements(true)], + ], + 'questions' => [ + 'type' => 'gauge', + ['labels' => ['answered' => true], 'value' => $this->stats->questions(true)], + ['labels' => ['answered' => false], 'value' => $this->stats->questions(false)], + ], + 'messages' => ['type' => 'gauge', $this->stats->messages()], + 'password_resets' => ['type' => 'gauge', $this->stats->passwordResets()], 'registration_enabled' => ['type' => 'gauge', $this->config->get('registration_enabled')], + 'sessions' => ['type' => 'gauge', $this->stats->sessions()], + 'log_entries' => [ + 'type' => 'counter', + [ + 'labels' => ['level' => LogLevel::EMERGENCY], + 'value' => $this->stats->logEntries(LogLevel::EMERGENCY) + ], + ['labels' => ['level' => LogLevel::ALERT], 'value' => $this->stats->logEntries(LogLevel::ALERT)], + ['labels' => ['level' => LogLevel::CRITICAL], 'value' => $this->stats->logEntries(LogLevel::CRITICAL)], + ['labels' => ['level' => LogLevel::ERROR], 'value' => $this->stats->logEntries(LogLevel::ERROR)], + ['labels' => ['level' => LogLevel::WARNING], 'value' => $this->stats->logEntries(LogLevel::WARNING)], + ['labels' => ['level' => LogLevel::NOTICE], 'value' => $this->stats->logEntries(LogLevel::NOTICE)], + ['labels' => ['level' => LogLevel::INFO], 'value' => $this->stats->logEntries(LogLevel::INFO)], + ['labels' => ['level' => LogLevel::DEBUG], 'value' => $this->stats->logEntries(LogLevel::DEBUG)], + ], ]; $data['scrape_duration_seconds'] = [ diff --git a/src/Controllers/Metrics/Stats.php b/src/Controllers/Metrics/Stats.php index 891f8c80..838411d1 100644 --- a/src/Controllers/Metrics/Stats.php +++ b/src/Controllers/Metrics/Stats.php @@ -96,6 +96,31 @@ class Stats return $query->count(); } + /** + * @param string $vehicle + * @return int + * @codeCoverageIgnore + */ + public function licenses($vehicle = null) + { + $mapping = [ + 'forklift' => 'has_license_forklift', + 'car' => 'has_license_car', + '3.5t' => 'has_license_3_5t_transporter', + '7.5t' => 'has_license_7_5t_truck', + '12.5t' => 'has_license_12_5t_truck', + ]; + + $query = $this + ->getQuery('UserDriverLicenses'); + + if (!is_null($vehicle)) { + $query->where($mapping[$vehicle], '=', true); + } + + return $query->count(); + } + /** * The number of worked shifts * @@ -121,6 +146,113 @@ class Stats return $query->sum($this->raw('end - start')); } + /** + * @return int + * @codeCoverageIgnore + */ + public function worklogSeconds() + { + return round($this + ->getQuery('UserWorkLog') + ->sum($this->raw('work_hours * 60*60'))); + } + + /** + * @return int + * @codeCoverageIgnore + */ + public function shifts() + { + return $this + ->getQuery('Shifts') + ->count(); + } + + /** + * @param bool $meeting + * @return int + * @codeCoverageIgnore + */ + public function announcements($meeting = null) + { + $query = $this + ->getQuery('News'); + + if (!is_null($meeting)) { + $query->where('Treffen', '=', $meeting); + } + + return $query->count(); + } + + /** + * @param bool $answered + * @return int + * @codeCoverageIgnore + */ + public function questions($answered = null) + { + $query = $this + ->getQuery('Questions'); + + if (!is_null($answered)) { + if ($answered) { + $query->whereNotNull('AID'); + } else { + $query->whereNull('AID'); + } + } + + return $query->count(); + } + + /** + * @return int + * @codeCoverageIgnore + */ + public function messages() + { + return $this + ->getQuery('Messages') + ->count(); + } + + /** + * @return int + */ + public function sessions() + { + return $this + ->getQuery('sessions') + ->count(); + } + + /** + * @param string $level + * @return int + */ + public function logEntries($level = null) + { + $query = $this + ->getQuery('log_entries'); + + if (!is_null($level)) { + $query->where('level', '=', $level); + } + + return $query->count(); + } + + /** + * @return int + */ + public function passwordResets() + { + return $this + ->getQuery('password_resets') + ->count(); + } + /** * @param string $table * @return QueryBuilder diff --git a/tests/Unit/Controllers/Metrics/ControllerTest.php b/tests/Unit/Controllers/Metrics/ControllerTest.php index 013a3352..37310ddf 100644 --- a/tests/Unit/Controllers/Metrics/ControllerTest.php +++ b/tests/Unit/Controllers/Metrics/ControllerTest.php @@ -11,6 +11,7 @@ use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Test\Unit\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LogLevel; use Symfony\Component\HttpFoundation\ServerBag; class ControllerTest extends TestCase @@ -36,9 +37,18 @@ class ControllerTest extends TestCase ->willReturnCallback(function ($path, $data) use ($response) { $this->assertEquals('/metrics', $path); $this->assertArrayHasKey('users', $data); + $this->assertArrayHasKey('licenses', $data); $this->assertArrayHasKey('users_working', $data); $this->assertArrayHasKey('work_seconds', $data); + $this->assertArrayHasKey('worklog_seconds', $data); + $this->assertArrayHasKey('shifts', $data); + $this->assertArrayHasKey('announcements', $data); + $this->assertArrayHasKey('questions', $data); + $this->assertArrayHasKey('messages', $data); + $this->assertArrayHasKey('password_resets', $data); $this->assertArrayHasKey('registration_enabled', $data); + $this->assertArrayHasKey('sessions', $data); + $this->assertArrayHasKey('log_entries', $data); $this->assertArrayHasKey('scrape_duration_seconds', $data); return 'metrics return'; @@ -53,6 +63,10 @@ class ControllerTest extends TestCase ->with('metrics return') ->willReturn($response); + $stats->expects($this->exactly(5)) + ->method('licenses') + ->withConsecutive(['forklift'], ['car'], ['3.5t'], ['7.5t'], ['12.5t']) + ->willReturnOnConsecutiveCalls(3, 15, 9, 7, 1); $stats->expects($this->exactly(2)) ->method('arrivedUsers') ->withConsecutive([false], [true]) @@ -65,7 +79,33 @@ class ControllerTest extends TestCase ->method('workSeconds') ->withConsecutive([true, false], [false, false], [null, true]) ->willReturnOnConsecutiveCalls(60 * 37, 60 * 251, 60 * 3); + $stats->expects($this->exactly(2)) + ->method('announcements') + ->withConsecutive([false], [true]) + ->willReturnOnConsecutiveCalls(18, 7); + $stats->expects($this->exactly(2)) + ->method('questions') + ->withConsecutive([true], [false]) + ->willReturnOnConsecutiveCalls(5, 0); + $stats->expects($this->exactly(8)) + ->method('logEntries') + ->withConsecutive( + [LogLevel::EMERGENCY], + [LogLevel::ALERT], + [LogLevel::CRITICAL], + [LogLevel::ERROR], + [LogLevel::WARNING], + [LogLevel::NOTICE], + [LogLevel::INFO], + [LogLevel::DEBUG] + ) + ->willReturnOnConsecutiveCalls(0, 1, 0, 5, 999, 4, 55, 3); $this->setExpects($stats, 'newUsers', null, 9); + $this->setExpects($stats, 'worklogSeconds', null, 39 * 60 * 60); + $this->setExpects($stats, 'shifts', null, 142); + $this->setExpects($stats, 'messages', null, 3); + $this->setExpects($stats, 'passwordResets', null, 1); + $this->setExpects($stats, 'sessions', null, 1234); $config->set('registration_enabled', 1); diff --git a/tests/Unit/Controllers/Metrics/StatsTest.php b/tests/Unit/Controllers/Metrics/StatsTest.php index 1618b99b..2ebe52ae 100644 --- a/tests/Unit/Controllers/Metrics/StatsTest.php +++ b/tests/Unit/Controllers/Metrics/StatsTest.php @@ -2,12 +2,16 @@ namespace Engelsystem\Test\Unit\Controllers\Metrics; +use Carbon\Carbon; use Engelsystem\Controllers\Metrics\Stats; +use Engelsystem\Models\LogEntry; +use Engelsystem\Models\User\PasswordReset; use Engelsystem\Models\User\State; use Engelsystem\Models\User\User; use Engelsystem\Test\Unit\HasDatabase; use Engelsystem\Test\Unit\TestCase; use Illuminate\Support\Str; +use Psr\Log\LogLevel; class StatsTest extends TestCase { @@ -39,6 +43,62 @@ class StatsTest extends TestCase $this->assertEquals(3, $stats->arrivedUsers()); } + /** + * @covers \Engelsystem\Controllers\Metrics\Stats::sessions + */ + public function testSessions() + { + $this->initDatabase(); + + $this->database + ->getConnection() + ->table('sessions') + ->insert([ + ['id' => 'asd', 'payload' => 'data', 'last_activity' => new Carbon('1 month ago')], + ['id' => 'efg', 'payload' => 'lorem', 'last_activity' => new Carbon('55 minutes ago')], + ['id' => 'hij', 'payload' => 'ipsum', 'last_activity' => new Carbon('3 seconds ago')], + ['id' => 'klm', 'payload' => 'dolor', 'last_activity' => new Carbon()], + ]); + + $stats = new Stats($this->database); + $this->assertEquals(4, $stats->sessions()); + } + + /** + * @covers \Engelsystem\Controllers\Metrics\Stats::logEntries + */ + public function testLogEntries() + { + $this->initDatabase(); + + (new LogEntry(['level' => LogLevel::INFO, 'message' => 'Some info']))->save(); + (new LogEntry(['level' => LogLevel::INFO, 'message' => 'Another info']))->save(); + (new LogEntry(['level' => LogLevel::CRITICAL, 'message' => 'A critical error!']))->save(); + (new LogEntry(['level' => LogLevel::DEBUG, 'message' => 'Verbose output!']))->save(); + (new LogEntry(['level' => LogLevel::INFO, 'message' => 'Shutdown initiated']))->save(); + (new LogEntry(['level' => LogLevel::WARNING, 'message' => 'Please be cautious']))->save(); + + $stats = new Stats($this->database); + $this->assertEquals(6, $stats->logEntries()); + $this->assertEquals(3, $stats->logEntries(LogLevel::INFO)); + $this->assertEquals(1, $stats->logEntries(LogLevel::DEBUG)); + } + + /** + * @covers \Engelsystem\Controllers\Metrics\Stats::passwordResets + */ + public function testPasswordResets() + { + $this->initDatabase(); + $this->addUsers(); + + (new PasswordReset(['use_id' => 1, 'token' => 'loremIpsum123']))->save(); + (new PasswordReset(['use_id' => 3, 'token' => '5omeR4nd0mTok3N']))->save(); + + $stats = new Stats($this->database); + $this->assertEquals(2, $stats->passwordResets()); + } + /** * Add some example users */