Metrics: Use buckets for work, worklog and vouchers
This commit is contained in:
parent
138ee996b0
commit
e4247cd0bd
|
@ -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,
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue