Metrics: Use buckets for work, worklog and vouchers

This commit is contained in:
Igor Scheller 2020-08-20 14:29:27 +02:00 committed by msquare
parent 138ee996b0
commit e4247cd0bd
6 changed files with 180 additions and 13 deletions

View File

@ -179,6 +179,12 @@ return [
'4XL' => '4XLarge Straight-Cut', '4XL' => '4XLarge Straight-Cut',
], ],
'metrics' => [
// User work buckets in seconds
'work' => [1 * 60 * 60, 1.5 * 60 * 60, 2 * 60 * 60, 3 * 60 * 60, 5 * 60 * 60, 10 * 60 * 60, 20 * 60 * 60],
'voucher' => [0, 1, 2, 3, 5, 10, 15, 20],
],
// Shifts overview // Shifts overview
// Set max number of hours that can be shown at once // Set max number of hours that can be shown at once
'filter_max_duration' => 0, 'filter_max_duration' => 0,

View File

@ -62,6 +62,11 @@ class Controller extends BaseController
{ {
$now = microtime(true); $now = microtime(true);
$this->checkAuth(); $this->checkAuth();
$metrics = $this->config->get('metrics');
foreach (['work', 'voucher'] as $type) {
sort($metrics[$type]);
$metrics[$type] = array_merge($metrics[$type], ['+Inf']);
}
$userTshirtSizes = $this->formatStats($this->stats->tshirtSizes(), 'tshirt_sizes', 'shirt_size', 'size'); $userTshirtSizes = $this->formatStats($this->stats->tshirtSizes(), 'tshirt_sizes', 'shirt_size', 'size');
$userLocales = $this->formatStats($this->stats->languages(), 'locales', 'language', 'locale'); $userLocales = $this->formatStats($this->stats->languages(), 'locales', 'language', 'locale');
@ -103,13 +108,32 @@ class Controller extends BaseController
['labels' => ['freeloader' => true], $this->stats->currentlyWorkingUsers(true)], ['labels' => ['freeloader' => true], $this->stats->currentlyWorkingUsers(true)],
], ],
'work_seconds' => [ 'work_seconds' => [
'type' => 'gauge', 'help' => 'Working users',
['labels' => ['state' => 'done'], 'value' => $this->stats->workSeconds(true, false)], 'type' => 'histogram',
['labels' => ['state' => 'planned'], 'value' => $this->stats->workSeconds(false, false)], [
['labels' => ['state' => 'freeloaded'], 'value' => $this->stats->workSeconds(null, true)], 'labels' => ['state' => 'done'],
'value' => $this->stats->workBuckets($metrics['work'], true, false),
'sum' => $this->stats->workSeconds(true, false),
],
[
'labels' => ['state' => 'planned'],
'value' => $this->stats->workBuckets($metrics['work'], false, false),
'sum' => $this->stats->workSeconds(false, false),
],
[
'labels' => ['state' => 'freeloaded'],
'value' => $this->stats->workBuckets($metrics['work'], null, true),
'sum' => $this->stats->workSeconds(null, true),
],
],
'worklog_seconds' => [
'type' => 'histogram',
$this->stats->worklogBuckets($metrics['work']) + ['sum' => $this->stats->worklogSeconds()],
],
'vouchers' => [
'type' => 'histogram',
$this->stats->vouchersBuckets($metrics['voucher']) + ['sum' => $this->stats->vouchers()],
], ],
'worklog_seconds' => ['type' => 'gauge', $this->stats->worklogSeconds()],
'vouchers' => ['type' => 'counter', $this->stats->vouchers()],
'tshirts_issued' => ['type' => 'counter', 'help' => 'Issued T-Shirts', $this->stats->tshirts()], 'tshirts_issued' => ['type' => 'counter', 'help' => 'Issued T-Shirts', $this->stats->tshirts()],
'tshirt_sizes' => [ 'tshirt_sizes' => [
'type' => 'gauge', 'type' => 'gauge',

View File

@ -110,12 +110,41 @@ class Stats
return $query->count(); return $query->count();
} }
/**
* @return QueryBuilder
*/
protected function vouchersQuery()
{
return State::query();
}
/** /**
* @return int * @return int
*/ */
public function vouchers(): int public function vouchers(): int
{ {
return (int)State::query()->sum('got_voucher'); return (int)$this->vouchersQuery()->sum('got_voucher');
}
/**
* @param array $buckets
*
* @return array
*/
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;
} }
/** /**
@ -190,10 +219,11 @@ class Stats
* *
* @param bool|null $done * @param bool|null $done
* @param bool|null $freeloaded * @param bool|null $freeloaded
* @return int *
* @codeCoverageIgnore * @codeCoverageIgnore
* @return QueryBuilder
*/ */
public function workSeconds(bool $done = null, bool $freeloaded = null): int protected function workSecondsQuery(bool $done = null, bool $freeloaded = null): QueryBuilder
{ {
$query = $this $query = $this
->getQuery('ShiftEntry') ->getQuery('ShiftEntry')
@ -207,18 +237,109 @@ class Stats
$query->where('end', ($done == true ? '<' : '>='), time()); $query->where('end', ($done == true ? '<' : '>='), time());
} }
return $query;
}
/**
* The number of worked shifts
*
* @param bool|null $done
* @param bool|null $freeloaded
*
* @return int
* @codeCoverageIgnore
*/
public function workSeconds(bool $done = null, bool $freeloaded = null): int
{
$query = $this->workSecondsQuery($done, $freeloaded);
return (int)$query->sum($this->raw('end - start')); return (int)$query->sum($this->raw('end - start'));
} }
/**
* The number of worked shifts
*
* @param array $buckets
* @param bool|null $done
* @param bool|null $freeloaded
*
* @return array
* @codeCoverageIgnore
*/
public function workBuckets(array $buckets, bool $done = null, bool $freeloaded = null): array
{
return $this->getBuckets(
$buckets,
$this->workSecondsQuery($done, $freeloaded),
'UID',
'SUM(end - start)',
'end - start'
);
}
/**
* @param array $buckets
* @param QueryBuilder $basicQuery
* @param string $groupBy
* @param string $having
* @param string $count
*
* @return array
* @codeCoverageIgnore As long as its only used for old tables
*/
protected function getBuckets(array $buckets, $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] = $query->count($this->raw($count));
}
return $return;
}
/**
* @return QueryBuilder
* @codeCoverageIgnore
*/
protected function worklogSecondsQuery()
{
return $this
->getQuery('UserWorkLog');
}
/** /**
* @return int * @return int
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function worklogSeconds(): int public function worklogSeconds(): int
{ {
return (int)$this return (int)$this->worklogSecondsQuery()
->getQuery('UserWorkLog') ->sum($this->raw('work_hours * 60 * 60'));
->sum($this->raw('work_hours * 60*60')); }
/**
* @param array $buckets
*
* @return array
* @codeCoverageIgnore
*/
public function worklogBuckets(array $buckets): array
{
return $this->getBuckets(
$buckets,
$this->worklogSecondsQuery(),
'user_id',
'SUM(work_hours * 60 * 60)',
'work_hours * 60 * 60'
);
} }
/** /**

View File

@ -12,7 +12,7 @@ class ControllerTest extends ApplicationFeatureTest
*/ */
public function testMetrics() public function testMetrics()
{ {
config(['api_key' => null]); config(['api_key' => null, 'metrics' => ['work' => [60 * 60], 'voucher' => [1]]]);
/** @var Controller $controller */ /** @var Controller $controller */
$controller = app()->make(Controller::class); $controller = app()->make(Controller::class);

View File

@ -151,6 +151,10 @@ class ControllerTest extends TestCase
'0' => 'Nothing', '0' => 'Nothing',
'1' => 'Testing', '1' => 'Testing',
]); ]);
$config->set('metrics', [
'work' => [60 * 60],
'voucher' => [1]
]);
$this->setExpects($version, 'getVersion', [], '0.42.42'); $this->setExpects($version, 'getVersion', [], '0.42.42');

View File

@ -36,6 +36,7 @@ class StatsTest extends TestCase
/** /**
* @covers \Engelsystem\Controllers\Metrics\Stats::vouchers * @covers \Engelsystem\Controllers\Metrics\Stats::vouchers
* @covers \Engelsystem\Controllers\Metrics\Stats::vouchersQuery
*/ */
public function testVouchers() public function testVouchers()
{ {
@ -45,6 +46,17 @@ class StatsTest extends TestCase
$this->assertEquals(14, $stats->vouchers()); $this->assertEquals(14, $stats->vouchers());
} }
/**
* @covers \Engelsystem\Controllers\Metrics\Stats::vouchersBuckets
*/
public function testVouchersBuckets()
{
$this->addUsers();
$stats = new Stats($this->database);
$this->assertEquals([1 => 6, 3 => 8, '+Inf' => 9], $stats->vouchersBuckets([1, 3, '+Inf']));
}
/** /**
* @covers \Engelsystem\Controllers\Metrics\Stats::tshirts * @covers \Engelsystem\Controllers\Metrics\Stats::tshirts
*/ */