diff --git a/app/Bus/Commands/Metric/AddMetricCommand.php b/app/Bus/Commands/Metric/AddMetricCommand.php index 39ed75e0..683caf1a 100644 --- a/app/Bus/Commands/Metric/AddMetricCommand.php +++ b/app/Bus/Commands/Metric/AddMetricCommand.php @@ -69,6 +69,13 @@ final class AddMetricCommand */ public $default_view; + /** + * The threshold to buffer the metric points in. + * + * @var int + */ + public $threshold; + /** * The validation rules. * @@ -84,6 +91,7 @@ final class AddMetricCommand 'display_chart' => 'int', 'places' => 'int|between:0,4', 'default_view' => 'int|between:0,3', + 'threshold' => 'numeric|between:0,10', ]; /** @@ -97,10 +105,11 @@ final class AddMetricCommand * @param int $display_chart * @param int $places * @param int $default_view + * @param int $threshold * * @return void */ - public function __construct($name, $suffix, $description, $default_value, $calc_type, $display_chart, $places, $default_view) + public function __construct($name, $suffix, $description, $default_value, $calc_type, $display_chart, $places, $default_view, $threshold) { $this->name = $name; $this->suffix = $suffix; @@ -110,5 +119,6 @@ final class AddMetricCommand $this->display_chart = $display_chart; $this->places = $places; $this->default_view = $default_view; + $this->threshold = $threshold; } } diff --git a/app/Bus/Commands/Metric/UpdateMetricCommand.php b/app/Bus/Commands/Metric/UpdateMetricCommand.php index 7c206ad5..c12b0ec0 100644 --- a/app/Bus/Commands/Metric/UpdateMetricCommand.php +++ b/app/Bus/Commands/Metric/UpdateMetricCommand.php @@ -78,6 +78,13 @@ final class UpdateMetricCommand */ public $default_view; + /** + * The threshold to buffer the metric points in. + * + * @var int + */ + public $threshold; + /** * The validation rules. * @@ -93,6 +100,7 @@ final class UpdateMetricCommand 'display_chart' => 'int', 'places' => 'numeric|between:0,4', 'default_view' => 'numeric|between:0,4', + 'threshold' => 'numeric|between:0,10', ]; /** @@ -107,10 +115,11 @@ final class UpdateMetricCommand * @param int $display_chart * @param int $places * @param int $default_view + * @param int $threshold * * @return void */ - public function __construct(Metric $metric, $name, $suffix, $description, $default_value, $calc_type, $display_chart, $places, $default_view) + public function __construct(Metric $metric, $name, $suffix, $description, $default_value, $calc_type, $display_chart, $places, $default_view, $threshold) { $this->metric = $metric; $this->name = $name; @@ -121,5 +130,6 @@ final class UpdateMetricCommand $this->display_chart = $display_chart; $this->places = $places; $this->default_view = $default_view; + $this->threshold = $threshold; } } diff --git a/app/Bus/Commands/Metric/UpdateMetricPointCommand.php b/app/Bus/Commands/Metric/UpdateMetricPointCommand.php index 232a0f18..34bff772 100644 --- a/app/Bus/Commands/Metric/UpdateMetricPointCommand.php +++ b/app/Bus/Commands/Metric/UpdateMetricPointCommand.php @@ -14,6 +14,11 @@ namespace CachetHQ\Cachet\Bus\Commands\Metric; use CachetHQ\Cachet\Models\Metric; use CachetHQ\Cachet\Models\MetricPoint; +/** + * This is the update metric point command. + * + * @author James Brooks + */ final class UpdateMetricPointCommand { /** @@ -33,7 +38,7 @@ final class UpdateMetricPointCommand /** * The metric point value. * - * @var int + * @var float */ public $value; @@ -50,7 +55,7 @@ final class UpdateMetricPointCommand * @var string[] */ public $rules = [ - 'value' => 'int', + 'value' => 'numeric', 'created_at' => 'string', ]; @@ -59,7 +64,7 @@ final class UpdateMetricPointCommand * * @param \CachetHQ\Cachet\Models\MetricPoint $point * @param \CachetHQ\Cachet\Models\Metric $metric - * @param int $value + * @param float $value * @param string $created_at * * @return void diff --git a/app/Bus/Handlers/Commands/Metric/AddMetricCommandHandler.php b/app/Bus/Handlers/Commands/Metric/AddMetricCommandHandler.php index cedeed12..487db4a7 100644 --- a/app/Bus/Handlers/Commands/Metric/AddMetricCommandHandler.php +++ b/app/Bus/Handlers/Commands/Metric/AddMetricCommandHandler.php @@ -35,6 +35,7 @@ class AddMetricCommandHandler 'display_chart' => $command->display_chart, 'places' => $command->places, 'default_view' => $command->default_view, + 'threshold' => $command->threshold, ]); event(new MetricWasAddedEvent($metric)); diff --git a/app/Bus/Handlers/Commands/Metric/AddMetricPointCommandHandler.php b/app/Bus/Handlers/Commands/Metric/AddMetricPointCommandHandler.php index dd07ef53..17b6dade 100644 --- a/app/Bus/Handlers/Commands/Metric/AddMetricPointCommandHandler.php +++ b/app/Bus/Handlers/Commands/Metric/AddMetricPointCommandHandler.php @@ -15,6 +15,7 @@ use CachetHQ\Cachet\Bus\Commands\Metric\AddMetricPointCommand; use CachetHQ\Cachet\Bus\Events\Metric\MetricPointWasAddedEvent; use CachetHQ\Cachet\Dates\DateFactory; use CachetHQ\Cachet\Models\MetricPoint; +use Carbon\Carbon; class AddMetricPointCommandHandler { @@ -49,19 +50,35 @@ class AddMetricPointCommandHandler $metric = $command->metric; $createdAt = $command->created_at; - $data = [ - 'metric_id' => $metric->id, - 'value' => $command->value, - ]; + // Do we have an existing point with the same value? + $point = $this->findOrCreatePoint($command); - if ($createdAt) { - $data['created_at'] = $this->dates->create('U', $createdAt)->format('Y-m-d H:i:s'); + $point->increment('counter', 1); + + event(new MetricPointWasAddedEvent($point)); + + return $point; + } + + protected function findOrCreatePoint(AddMetricPointCommand $command) + { + $buffer = Carbon::now()->subMinutes($command->metric->threshold); + $point = MetricPoint::where('metric_id', $command->metric->id)->where('value', $command->value)->where('created_at', '>=', $buffer)->first(); + + if ($point) { + return $point; } - $metricPoint = MetricPoint::create($data); + $data = [ + 'metric_id' => $command->metric->id, + 'value' => $command->value, + 'counter' => 0, + ]; - event(new MetricPointWasAddedEvent($metricPoint)); + if ($command->created_at) { + $data['created_at'] = $this->dates->create('U', $command->created_at)->format('Y-m-d H:i:s'); + } - return $metricPoint; + return MetricPoint::create($data); } } diff --git a/app/Bus/Handlers/Commands/Metric/UpdateMetricCommandHandler.php b/app/Bus/Handlers/Commands/Metric/UpdateMetricCommandHandler.php index cb18ddba..d354bf69 100644 --- a/app/Bus/Handlers/Commands/Metric/UpdateMetricCommandHandler.php +++ b/app/Bus/Handlers/Commands/Metric/UpdateMetricCommandHandler.php @@ -53,6 +53,7 @@ class UpdateMetricCommandHandler 'display_chart' => $command->display_chart, 'places' => $command->places, 'default_view' => $command->default_view, + 'threshold' => $command->threshold, ]; return array_filter($params, function ($val) { diff --git a/app/Bus/Handlers/Commands/Metric/UpdateMetricPointCommandHandler.php b/app/Bus/Handlers/Commands/Metric/UpdateMetricPointCommandHandler.php index 35337db3..aec34b63 100644 --- a/app/Bus/Handlers/Commands/Metric/UpdateMetricPointCommandHandler.php +++ b/app/Bus/Handlers/Commands/Metric/UpdateMetricPointCommandHandler.php @@ -51,7 +51,7 @@ class UpdateMetricPointCommandHandler $data = [ 'metric_id' => $metric->id, - 'value' => $command->value, + 'value' => (float) $command->value, ]; if ($createdAt) { diff --git a/app/Http/Controllers/Api/MetricController.php b/app/Http/Controllers/Api/MetricController.php index c74907ce..f893ee4f 100644 --- a/app/Http/Controllers/Api/MetricController.php +++ b/app/Http/Controllers/Api/MetricController.php @@ -84,7 +84,8 @@ class MetricController extends AbstractApiController Binput::get('calc_type', 0), Binput::get('display_chart', true), Binput::get('places', 2), - Binput::get('view', 1) + Binput::get('view', 1), + Binput::get('threshold', 5) )); } catch (QueryException $e) { throw new BadRequestHttpException(); @@ -112,7 +113,8 @@ class MetricController extends AbstractApiController Binput::get('calc_type'), Binput::get('display_chart'), Binput::get('places'), - Binput::get('view') + Binput::get('view'), + Binput::get('threshold', 5) )); } catch (QueryException $e) { throw new BadRequestHttpException(); diff --git a/app/Http/Controllers/Api/MetricPointController.php b/app/Http/Controllers/Api/MetricPointController.php index 9ffc6018..a84758d9 100644 --- a/app/Http/Controllers/Api/MetricPointController.php +++ b/app/Http/Controllers/Api/MetricPointController.php @@ -48,8 +48,8 @@ class MetricPointController extends AbstractApiController $metricPoint = dispatch(new AddMetricPointCommand( $metric, Binput::get('value'), - Binput::get('timestamp')) - ); + Binput::get('timestamp') + )); } catch (QueryException $e) { throw new BadRequestHttpException(); } diff --git a/app/Http/Controllers/Dashboard/MetricController.php b/app/Http/Controllers/Dashboard/MetricController.php index a3bc2ce4..0e1d2e5b 100644 --- a/app/Http/Controllers/Dashboard/MetricController.php +++ b/app/Http/Controllers/Dashboard/MetricController.php @@ -79,7 +79,8 @@ class MetricController extends Controller $metricData['calc_type'], $metricData['display_chart'], $metricData['places'], - $metricData['default_view'] + $metricData['default_view'], + $metricData['threshold'] )); } catch (ValidationException $e) { return Redirect::route('dashboard.metrics.add') @@ -151,7 +152,8 @@ class MetricController extends Controller Binput::get('calc_type', null, false), Binput::get('display_chart', null, false), Binput::get('places', null, false), - Binput::get('default_view', null, false) + Binput::get('default_view', null, false), + Binput::get('threshold', null, false) )); } catch (ValidationException $e) { return Redirect::route('dashboard.metrics.edit', ['id' => $metric->id]) diff --git a/app/Models/Metric.php b/app/Models/Metric.php index 5a14c9c4..83580691 100644 --- a/app/Models/Metric.php +++ b/app/Models/Metric.php @@ -47,6 +47,7 @@ class Metric extends Model implements HasPresenter 'calc_type' => 0, 'places' => 2, 'default_view' => 1, + 'threshold' => 5, ]; /** @@ -61,6 +62,7 @@ class Metric extends Model implements HasPresenter 'calc_type' => 'int', 'places' => 'int', 'default_view' => 'int', + 'threshold' => 'int', ]; /** @@ -77,6 +79,7 @@ class Metric extends Model implements HasPresenter 'calc_type', 'places', 'default_view', + 'threshold', ]; /** @@ -91,6 +94,7 @@ class Metric extends Model implements HasPresenter 'default_value' => 'numeric', 'places' => 'numeric|between:0,4', 'default_view' => 'numeric|between:0,3', + 'threshold' => 'numeric|between:0,10', ]; /** diff --git a/app/Models/MetricPoint.php b/app/Models/MetricPoint.php index 309819af..fadaae07 100644 --- a/app/Models/MetricPoint.php +++ b/app/Models/MetricPoint.php @@ -20,6 +20,16 @@ class MetricPoint extends Model implements HasPresenter { use ValidatingTrait; + /** + * The model's attributes. + * + * @var string[] + */ + protected $attributes = [ + 'value' => 0, + 'counter' => 1, + ]; + /** * The attributes that should be casted to native types. * @@ -27,7 +37,8 @@ class MetricPoint extends Model implements HasPresenter */ protected $casts = [ 'metric_id' => 'int', - 'value' => 'int', + 'value' => 'float', + 'counter' => 'int', ]; /** @@ -35,7 +46,12 @@ class MetricPoint extends Model implements HasPresenter * * @var string[] */ - protected $fillable = ['metric_id', 'value', 'created_at']; + protected $fillable = [ + 'metric_id', + 'value', + 'counter', + 'created_at', + ]; /** * The validation rules. @@ -53,7 +69,25 @@ class MetricPoint extends Model implements HasPresenter */ public function metric() { - return $this->belongsTo(Metric::class, 'id', 'metric_id'); + return $this->belongsTo(Metric::class); + } + + /** + * Override the value attribute. + * + * @param mixed $value + * + * @return float + */ + public function getActiveValueAttribute($value) + { + if ($this->metric->calc_type === Metric::CALC_SUM) { + return round((float) $value * $this->counter, $this->metric->places); + } elseif ($this->metric->calc_type === Metric::CALC_AVG) { + return round((float) $value * $this->counter, $this->metric->places); + } + + return round((float) $value, $this->metric->places); } /** diff --git a/app/Presenters/MetricPointPresenter.php b/app/Presenters/MetricPointPresenter.php index 5cbfdd60..388ef816 100644 --- a/app/Presenters/MetricPointPresenter.php +++ b/app/Presenters/MetricPointPresenter.php @@ -19,6 +19,16 @@ class MetricPointPresenter extends BasePresenter implements Arrayable { use TimestampsTrait; + /** + * Show the actual calculated value; as per (value * counter). + * + * @return int + */ + public function calculated_value() + { + return $this->wrappedObject->value * $this->wrappedObject->counter; + } + /** * Convert the presenter instance to an array. * @@ -27,8 +37,9 @@ class MetricPointPresenter extends BasePresenter implements Arrayable public function toArray() { return array_merge($this->wrappedObject->toArray(), [ - 'created_at' => $this->created_at(), - 'updated_at' => $this->updated_at(), + 'created_at' => $this->created_at(), + 'updated_at' => $this->updated_at(), + 'calculated_value' => $this->calculated_value(), ]); } } diff --git a/app/Repositories/Metric/MetricRepository.php b/app/Repositories/Metric/MetricRepository.php index b91e15c9..315ffc6e 100644 --- a/app/Repositories/Metric/MetricRepository.php +++ b/app/Repositories/Metric/MetricRepository.php @@ -105,7 +105,7 @@ class MetricRepository $points = []; - $pointKey = $dateTime->format('jS M'); + $pointKey = $dateTime->format('D jS M'); for ($i = 0; $i <= 7; $i++) { $points[$pointKey] = $this->repository->getPointsForDayInWeek($metric, $i); diff --git a/app/Repositories/Metric/MySqlRepository.php b/app/Repositories/Metric/MySqlRepository.php index 6cd0904c..980a67a0 100644 --- a/app/Repositories/Metric/MySqlRepository.php +++ b/app/Repositories/Metric/MySqlRepository.php @@ -32,14 +32,21 @@ class MySqlRepository implements MetricInterface $dateTime = (new Date())->sub(new DateInterval('PT'.$hour.'H'))->sub(new DateInterval('PT'.$minute.'M')); $timeInterval = $dateTime->format('YmdHi'); - $points = $metric->points() - ->whereRaw('DATE_FORMAT(created_at, "%Y%m%d%H%i") = '.$timeInterval) - ->groupBy(DB::raw('HOUR(created_at), MINUTE(created_at)')); - if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $value = $points->sum('value'); + $queryType = 'SUM(mp.`value` * mp.`counter`) AS `value`'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $value = $points->avg('value'); + $queryType = 'AVG(mp.`value` * mp.`counter`) AS `value`'; + } + + $value = 0; + + $points = DB::select("SELECT {$queryType} FROM metrics m INNER JOIN metric_points mp ON m.id = mp.metric_id WHERE m.id = :metricId AND DATE_FORMAT(mp.`created_at`, '%Y%m%d%H%i') = :timeInterval GROUP BY HOUR(mp.`created_at`), MINUTE(mp.`created_at`)", [ + 'metricId' => $metric->id, + 'timeInterval' => $timeInterval, + ]); + + if (isset($points[0]) && !($value = $points[0]->value)) { + $value = 0; } if ($value === 0 && $metric->default_value != $value) { @@ -62,14 +69,21 @@ class MySqlRepository implements MetricInterface $dateTime = (new Date())->sub(new DateInterval('PT'.$hour.'H')); $hourInterval = $dateTime->format('YmdH'); - $points = $metric->points() - ->whereRaw('DATE_FORMAT(created_at, "%Y%m%d%H") = '.$hourInterval) - ->groupBy(DB::raw('HOUR(created_at)')); - if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $value = $points->sum('value'); + $queryType = 'SUM(mp.`value` * mp.`counter`) AS `value`'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $value = $points->avg('value'); + $queryType = 'AVG(mp.`value` * mp.`counter`) AS `value`'; + } + + $value = 0; + + $points = DB::select("SELECT {$queryType} FROM metrics m INNER JOIN metric_points mp ON m.id = mp.metric_id WHERE m.id = :metricId AND DATE_FORMAT(mp.`created_at`, '%Y%m%d%H') = :hourInterval GROUP BY HOUR(mp.`created_at`)", [ + 'metricId' => $metric->id, + 'hourInterval' => $hourInterval, + ]); + + if (isset($points[0]) && !($value = $points[0]->value)) { + $value = 0; } if ($value === 0 && $metric->default_value != $value) { @@ -90,15 +104,21 @@ class MySqlRepository implements MetricInterface { $dateTime = (new Date())->sub(new DateInterval('P'.$day.'D')); - $points = $metric->points() - ->whereRaw('created_at BETWEEN DATE_SUB(created_at, INTERVAL 1 WEEK) AND NOW()') - ->whereRaw('DATE_FORMAT(created_at, "%Y%m%d") = '.$dateTime->format('Ymd')) - ->groupBy(DB::raw('DATE_FORMAT(created_at, "%Y%m%d")')); - if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $value = $points->sum('value'); + $queryType = 'SUM(mp.`value` * mp.`counter`) AS `value`'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $value = $points->avg('value'); + $queryType = 'AVG(mp.`value` * mp.`counter`) AS `value`'; + } + + $value = 0; + + $points = DB::select("SELECT {$queryType} FROM metrics m INNER JOIN metric_points mp ON m.id = mp.metric_id WHERE m.id = :metricId AND mp.`created_at` BETWEEN DATE_SUB(mp.`created_at`, INTERVAL 1 WEEK) AND DATE_ADD(NOW(), INTERVAL 1 DAY) AND DATE_FORMAT(mp.`created_at`, '%Y%m%d') = :timeInterval GROUP BY DATE_FORMAT(mp.`created_at`, '%Y%m%d')", [ + 'metricId' => $metric->id, + 'timeInterval' => $dateTime->format('Ymd'), + ]); + + if (isset($points[0]) && !($value = $points[0]->value)) { + $value = 0; } if ($value === 0 && $metric->default_value != $value) { diff --git a/app/Repositories/Metric/PgSqlRepository.php b/app/Repositories/Metric/PgSqlRepository.php index 7e7514cb..d3ec8cbc 100644 --- a/app/Repositories/Metric/PgSqlRepository.php +++ b/app/Repositories/Metric/PgSqlRepository.php @@ -30,26 +30,24 @@ class PgSqlRepository implements MetricInterface public function getPointsLastHour(Metric $metric, $hour, $minute) { $dateTime = (new Date())->sub(new DateInterval('PT'.$hour.'H'))->sub(new DateInterval('PT'.$minute.'M')); - $hourInterval = $dateTime->format('YmdHi'); // Default metrics calculations. if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $queryType = 'sum(metric_points.value)'; + $queryType = 'sum(metric_points.value * metric_points.counter)'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $queryType = 'avg(metric_points.value)'; + $queryType = 'avg(metric_points.value * metric_points.counter)'; } else { - $queryType = 'sum(metric_points.value)'; + $queryType = 'sum(metric_points.value * metric_points.counter)'; } - $query = DB::select("select {$queryType} as aggregate FROM metrics JOIN metric_points ON metric_points.metric_id = metrics.id WHERE metrics.id = :metric_id AND to_char(metric_points.created_at, 'YYYYMMDDHH24MI') = :timestamp GROUP BY to_char(metric_points.created_at, 'HHMI')", [ - 'metric_id' => $metric->id, - 'timestamp' => $hourInterval, + $value = 0; + $query = DB::select("select {$queryType} as value FROM metrics JOIN metric_points ON metric_points.metric_id = metrics.id WHERE metrics.id = :metricId AND to_char(metric_points.created_at, 'YYYYMMDDHH24MI') = :timeInterval GROUP BY to_char(metric_points.created_at, 'HHMI')", [ + 'metricId' => $metric->id, + 'timeInterval' => $dateTime->format('YmdHi'), ]); if (isset($query[0])) { - $value = $query[0]->aggregate; - } else { - $value = 0; + $value = $query[0]->value; } if ($value === 0 && $metric->default_value != $value) { @@ -70,26 +68,24 @@ class PgSqlRepository implements MetricInterface public function getPointsByHour(Metric $metric, $hour) { $dateTime = (new Date())->sub(new DateInterval('PT'.$hour.'H')); - $hourInterval = $dateTime->format('YmdH'); // Default metrics calculations. if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $queryType = 'sum(metric_points.value)'; + $queryType = 'sum(metric_points.value * metric_points.counter)'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $queryType = 'avg(metric_points.value)'; + $queryType = 'avg(metric_points.value * metric_points.counter)'; } else { - $queryType = 'sum(metric_points.value)'; + $queryType = 'sum(metric_points.value * metric_points.counter)'; } - $query = DB::select("select {$queryType} as aggregate FROM metrics JOIN metric_points ON metric_points.metric_id = metrics.id WHERE metric_points.metric_id = :metric_id AND to_char(metric_points.created_at, 'YYYYMMDDHH24') = :timestamp GROUP BY to_char(metric_points.created_at, 'H')", [ - 'metric_id' => $metric->id, - 'timestamp' => $hourInterval, + $value = 0; + $query = DB::select("select {$queryType} as value FROM metrics JOIN metric_points ON metric_points.metric_id = metrics.id WHERE metric_points.metric_id = :metricId AND to_char(metric_points.created_at, 'YYYYMMDDHH24') = :timeInterval GROUP BY to_char(metric_points.created_at, 'H')", [ + 'metricId' => $metric->id, + 'timeInterval' => $dateTime->format('YmdH'), ]); if (isset($query[0])) { - $value = $query[0]->aggregate; - } else { - $value = 0; + $value = $query[0]->value; } if ($value === 0 && $metric->default_value != $value) { @@ -110,15 +106,20 @@ class PgSqlRepository implements MetricInterface { $dateTime = (new Date())->sub(new DateInterval('P'.$day.'D')); - $points = $metric->points() - ->whereRaw('created_at BETWEEN (created_at - interval \'1 week\') AND now()') - ->whereRaw('to_char(created_at, \'YYYYMMDD\') = \''.$dateTime->format('Ymd').'\'') - ->groupBy(DB::raw('to_char(created_at, \'YYYYMMDD\')')); - if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $value = $points->sum('value'); + $queryType = 'sum(mp.`value` * mp.`counter`) AS `value`'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $value = $points->avg('value'); + $queryType = 'avg(mp.`value` * mp.`counter`) AS `value`'; + } + + $value = 0; + $points = DB::select("SELECT {$queryType} FROM metrics m INNER JOIN metric_points mp ON m.id = mp.metric_id WHERE m.id = :metricId AND mp.`created_at` BETWEEN (mp.`created_at` - interval '1 week') AND (now() + interval 1 day) AND to_char(mp.`created_at`, 'YYYYMMDD') = :timeInterval GROUP BY to_char(mp.`created_at`, 'YYYYMMDD')", [ + 'metricId' => $metric->id, + 'timeInterval' => $dateTime->format('Ymd'), + ]); + + if (isset($points[0]) && !($value = $points[0]->value)) { + $value = 0; } if ($value === 0 && $metric->default_value != $value) { diff --git a/app/Repositories/Metric/SqliteRepository.php b/app/Repositories/Metric/SqliteRepository.php index b2fd5af9..6467b606 100644 --- a/app/Repositories/Metric/SqliteRepository.php +++ b/app/Repositories/Metric/SqliteRepository.php @@ -30,16 +30,24 @@ class SqliteRepository implements MetricInterface public function getPointsLastHour(Metric $metric, $hour, $minute) { $dateTime = (new Date())->sub(new DateInterval('PT'.$hour.'H'))->sub(new DateInterval('PT'.$minute.'M')); - $hourInterval = $dateTime->format('YmdHi'); - - $points = $metric->points() - ->whereRaw('strftime("%Y%m%d%H%M", created_at) = "'.$hourInterval.'"') - ->groupBy(DB::raw('strftime("%H%M", created_at)')); + // Default metrics calculations. if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $value = $points->sum('value'); + $queryType = 'sum(metric_points.value * metric_points.counter)'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $value = $points->avg('value'); + $queryType = 'avg(metric_points.value * metric_points.counter)'; + } else { + $queryType = 'sum(metric_points.value * metric_points.counter)'; + } + + $value = 0; + $query = DB::select("select {$queryType} as value FROM metrics JOIN metric_points ON metric_points.metric_id = metrics.id WHERE metrics.id = :metricId AND strftime('%Y%m%d%H%M', metric_points.created_at) = :timeInterval GROUP BY strftime('%H%M', metric_points.created_at)", [ + 'metricId' => $metric->id, + 'timeInterval' => $dateTime->format('YmdHi'), + ]); + + if (isset($query[0])) { + $value = $query[0]->value; } if ($value === 0 && $metric->default_value != $value) { @@ -60,16 +68,24 @@ class SqliteRepository implements MetricInterface public function getPointsByHour(Metric $metric, $hour) { $dateTime = (new Date())->sub(new DateInterval('PT'.$hour.'H')); - $hourInterval = $dateTime->format('YmdH'); - - $points = $metric->points() - ->whereRaw('strftime("%Y%m%d%H", created_at) = "'.$hourInterval.'"') - ->groupBy(DB::raw('strftime("%H", created_at)')); + // Default metrics calculations. if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $value = $points->sum('value'); + $queryType = 'sum(metric_points.value * metric_points.counter)'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $value = $points->avg('value'); + $queryType = 'avg(metric_points.value * metric_points.counter)'; + } else { + $queryType = 'sum(metric_points.value * metric_points.counter)'; + } + + $value = 0; + $query = DB::select("select {$queryType} as value FROM metrics JOIN metric_points ON metric_points.metric_id = metrics.id WHERE metrics.id = :metricId AND strftime('%Y%m%d%H', metric_points.created_at) = :timeInterval GROUP BY strftime('%H', metric_points.created_at)", [ + 'metricId' => $metric->id, + 'timeInterval' => $dateTime->format('YmdH'), + ]); + + if (isset($query[0])) { + $value = $query[0]->value; } if ($value === 0 && $metric->default_value != $value) { @@ -90,15 +106,23 @@ class SqliteRepository implements MetricInterface { $dateTime = (new Date())->sub(new DateInterval('P'.$day.'D')); - $points = $metric->points() - ->whereRaw('created_at > date("now", "-7 day")') - ->whereRaw('strftime("%Y%m%d", created_at) = "'.$dateTime->format('Ymd').'"') - ->groupBy(DB::raw('strftime("%Y%m%d", created_at)')); - + // Default metrics calculations. if (!isset($metric->calc_type) || $metric->calc_type == Metric::CALC_SUM) { - $value = $points->sum('value'); + $queryType = 'sum(metric_points.value * metric_points.counter)'; } elseif ($metric->calc_type == Metric::CALC_AVG) { - $value = $points->avg('value'); + $queryType = 'avg(metric_points.value * metric_points.counter)'; + } else { + $queryType = 'sum(metric_points.value * metric_points.counter)'; + } + + $value = 0; + $query = DB::select("select {$queryType} as value FROM metrics JOIN metric_points ON metric_points.metric_id = metrics.id WHERE metrics.id = :metricId AND metric_points.created_at > date('now', '-7 day') AND strftime('%Y%m%d', metric_points.created_at) = :timeInterval GROUP BY strftime('%Y%m%d', metric_points.created_at)", [ + 'metricId' => $metric->id, + 'timeInterval' => $dateTime->format('Ymd'), + ]); + + if (isset($query[0])) { + $value = $query[0]->value; } if ($value === 0 && $metric->default_value != $value) { diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index f043ffe3..15862fa3 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -61,8 +61,10 @@ $factory->define(Metric::class, function ($faker) { 'suffix' => $faker->word(), 'description' => $faker->paragraph(), 'default_value' => 1, + 'places' => 2, 'calc_type' => $faker->boolean(), 'display_chart' => $faker->boolean(), + 'threshold' => 5, ]; }); @@ -70,6 +72,7 @@ $factory->define(MetricPoint::class, function ($faker) { return [ 'metric_id' => factory(Metric::class)->create()->id, 'value' => random_int(1, 100), + 'counter' => 1, ]; }); diff --git a/database/migrations/2016_03_01_174858_AlterTableMetricPointsAddCounterColumn.php b/database/migrations/2016_03_01_174858_AlterTableMetricPointsAddCounterColumn.php new file mode 100644 index 00000000..a6feaa55 --- /dev/null +++ b/database/migrations/2016_03_01_174858_AlterTableMetricPointsAddCounterColumn.php @@ -0,0 +1,49 @@ +integer('threshold')->unsigned()->default(5)->after('default_view'); + }); + + Schema::table('metric_points', function (Blueprint $table) { + $table->integer('counter')->unsigned()->default(1)->after('value'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('metrics', function (Blueprint $table) { + $table->dropColumn('threshold'); + }); + + Schema::table('metric_points', function (Blueprint $table) { + $table->dropColumn('counter'); + }); + } +} diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index 10dc1d7a..4d024597 100755 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -89,6 +89,7 @@ return [ 'type_avg' => 'Average', 'places' => 'Decimal places', 'default_view' => 'Default view', + 'threshold' => 'How many minutes of threshold between metric points?', 'points' => [ 'value' => 'Value', diff --git a/resources/views/dashboard/metrics/add.blade.php b/resources/views/dashboard/metrics/add.blade.php index 706c5706..ac42e9c1 100644 --- a/resources/views/dashboard/metrics/add.blade.php +++ b/resources/views/dashboard/metrics/add.blade.php @@ -55,6 +55,10 @@ +
+ + +
+
+ + +