Refactored the way we store metrics
This commit is contained in:
committed by
James Brooks
parent
3730ca8811
commit
f9bc46b460
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <james@alt-three.com>
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -51,7 +51,7 @@ class UpdateMetricPointCommandHandler
|
||||
|
||||
$data = [
|
||||
'metric_id' => $metric->id,
|
||||
'value' => $command->value,
|
||||
'value' => (float) $command->value,
|
||||
];
|
||||
|
||||
if ($createdAt) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Cachet.
|
||||
*
|
||||
* (c) Alt Three Services Limited
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AlterTableMetricPointsAddCounterColumn extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('metrics', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
<label for="metric-places">{{ trans('forms.metrics.places') }}</label>
|
||||
<input type="number" min="0" max="4" class="form-control" name="metric[places]" id="metric-places" required value="{{ Binput::old('metric.places') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="metric-places">{{ trans('forms.metrics.threshold') }}</label>
|
||||
<input type="number" min="0" max="100" class="form-control" name="metric[threshold]" id="metric-threshold" required value="{{ Binput::old('metric.threshold') }}">
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="hidden" value="0" name="metric[display_chart]">
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
<label for="metric-places">{{ trans('forms.metrics.places') }}</label>
|
||||
<input type="number" min="0" max="4" class="form-control" name="places" id="metric-places" required value="{{ $metric->places }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="metric-places">{{ trans('forms.metrics.threshold') }}</label>
|
||||
<input type="number" min="0" max="100" class="form-control" name="threshold" id="metric-threshold" required value="{{ $metric->threshold }}">
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="hidden" value="0" name="display_chart">
|
||||
|
||||
@@ -29,9 +29,12 @@ class MetricPointTest extends AbstractApiTestCase
|
||||
]);
|
||||
|
||||
$this->get("/api/v1/metrics/{$metric->id}/points");
|
||||
|
||||
$this->seeJson(['id' => $metricPoint[0]->id]);
|
||||
$this->seeJson(['id' => $metricPoint[1]->id]);
|
||||
$this->seeJson(['id' => $metricPoint[2]->id]);
|
||||
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testPostMetricPointUnauthorized()
|
||||
@@ -55,7 +58,10 @@ class MetricPointTest extends AbstractApiTestCase
|
||||
]);
|
||||
|
||||
$this->post("/api/v1/metrics/{$metric->id}/points", $metricPoint->toArray());
|
||||
|
||||
$this->seeJson(['value' => $metricPoint->value]);
|
||||
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testPostMetricPointTimestamp()
|
||||
@@ -72,7 +78,10 @@ class MetricPointTest extends AbstractApiTestCase
|
||||
$postData['timestamp'] = $timestamp;
|
||||
|
||||
$this->post("/api/v1/metrics/{$metric->id}/points", $postData);
|
||||
|
||||
$this->seeJson(['value' => $metricPoint->value, 'created_at' => $datetime]);
|
||||
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testPostMetricPointTimestampTimezone()
|
||||
@@ -96,7 +105,10 @@ class MetricPointTest extends AbstractApiTestCase
|
||||
$postData['timestamp'] = $datetime->timestamp;
|
||||
|
||||
$this->post("/api/v1/metrics/{$metric->id}/points", $postData, ['Time-Zone' => $timezone]);
|
||||
|
||||
$this->seeJson(['value' => $metricPoint->value, 'created_at' => $datetime->toDateTimeString()]);
|
||||
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testPutMetricPoint()
|
||||
@@ -106,10 +118,14 @@ class MetricPointTest extends AbstractApiTestCase
|
||||
$metricPoint = factory('CachetHQ\Cachet\Models\MetricPoint')->create([
|
||||
'metric_id' => $metric->id,
|
||||
]);
|
||||
|
||||
$this->put("/api/v1/metrics/{$metric->id}/points/{$metricPoint->id}", [
|
||||
'value' => 999,
|
||||
]);
|
||||
|
||||
$this->seeJson(['value' => 999]);
|
||||
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testDeleteMetricPoint()
|
||||
@@ -119,7 +135,9 @@ class MetricPointTest extends AbstractApiTestCase
|
||||
$metricPoint = factory('CachetHQ\Cachet\Models\MetricPoint')->create([
|
||||
'metric_id' => $metric->id,
|
||||
]);
|
||||
|
||||
$this->delete("/api/v1/metrics/{$metric->id}/points/{$metricPoint->id}");
|
||||
|
||||
$this->assertResponseStatus(204);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,15 @@ class AddMetricCommandTest extends AbstractTestCase
|
||||
protected function getObjectAndParams()
|
||||
{
|
||||
$params = [
|
||||
'name' => 'Coffee',
|
||||
'suffix' => 'cups',
|
||||
'description' => 'Cups of coffee consumed',
|
||||
'default_value' => 0,
|
||||
'calc_type' => 0,
|
||||
'display_chart' => 1,
|
||||
'places' => 0,
|
||||
'default_view' => 0,
|
||||
'name' => 'Coffee',
|
||||
'suffix' => 'cups',
|
||||
'description' => 'Cups of coffee consumed',
|
||||
'default_value' => 0,
|
||||
'calc_type' => 0,
|
||||
'display_chart' => 1,
|
||||
'places' => 0,
|
||||
'default_view' => 0,
|
||||
'threshold' => 0,
|
||||
];
|
||||
|
||||
$object = new AddMetricCommand(
|
||||
@@ -47,7 +48,8 @@ class AddMetricCommandTest extends AbstractTestCase
|
||||
$params['calc_type'],
|
||||
$params['display_chart'],
|
||||
$params['places'],
|
||||
$params['default_view']
|
||||
$params['default_view'],
|
||||
$params['threshold']
|
||||
);
|
||||
|
||||
return compact('params', 'object');
|
||||
|
||||
@@ -39,6 +39,7 @@ class UpdateMetricCommandTest extends AbstractTestCase
|
||||
'display_chart' => 1,
|
||||
'places' => 0,
|
||||
'default_view' => 0,
|
||||
'threshold' => 0,
|
||||
];
|
||||
|
||||
$object = new UpdateMetricCommand(
|
||||
@@ -50,7 +51,8 @@ class UpdateMetricCommandTest extends AbstractTestCase
|
||||
$params['calc_type'],
|
||||
$params['display_chart'],
|
||||
$params['places'],
|
||||
$params['default_view']
|
||||
$params['default_view'],
|
||||
$params['threshold']
|
||||
);
|
||||
|
||||
return compact('params', 'object');
|
||||
|
||||
Reference in New Issue
Block a user