Commit 30689312 authored by Mark Harding's avatar Mark Harding

(wip): structure for dashboards

parent 118395de
Pipeline #84396449 failed with stages
in 4 minutes and 31 seconds
<?php
namespace Minds\Core\Analytics\Dashboards;
interface DashboardCollectionInteface
{
/**
* Export everything in the collection
* @param array $extras
* @return array
*/
public function export(array $extras = []): array;
}
<?php
namespace Minds\Core\Analytics\Dashboards;
interface DashboardInterface
{
/**
* Build the dashboard
* NOTE: return type not specified to to php
* having terrible typing support
* @return self
*/
public function build();
/**
* Export
* @param array $extras
* @return array
*/
public function export(array $extras = []): array;
}
<?php
namespace Minds\Core\Analytics\Dashboards\Filters;
use Minds\Traits\MagicAttributes;
abstract class FilterAbstract
{
use MagicAttributes;
/** @var string */
protected $id;
/** @var string */
protected $label;
/** @var FilterOptions */
protected $options;
/** @var string */
protected $selectedOption;
/**
* Export
* @param array $extras
* @return array
*/
public function export($extras = []): array
{
return [
'id' => (string) $this->id,
'label' => (string) $this->label,
'options' => (array) $this->options->export(),
];
}
}
<?php
/**
*
*/
namespace Minds\Core\Analytics\Dashboards\Filters;
use Minds\Traits\MagicAttributes;
class FilterOptions
{
use MagicAttributes;
/** @var FilterOptionsOption[] */
private $options = [];
/**
* Set options
* @param FilterOptionsOption $options
* @return self
*/
public function setOptions(FilterOptionsOption ...$options): self
{
$this->options = $options;
return $this;
}
/**
* Export
* @param array $export
* @return array
*/
public function export(array $export = []): array
{
$options = [];
foreach ($this->options as $option) {
$options[] = $option->export();
}
return $options;
}
}
<?php
/**
*
*/
namespace Minds\Core\Analytics\Dashboards\Filters;
use Minds\Traits\MagicAttributes;
class FilterOptionsOption
{
use MagicAttributes;
/** @var string */
private $id;
/** @var string */
private $label;
/** @var bool */
private $available = true;
/** @var bool */
private $selected = false;
/**
* Export
* @param array $export
* @return array
*/
public function export(array $export = []): array
{
return [
'id' => $this->id,
'label' => $this->label,
'available' => (bool) $this->available,
'selected' => (bool) $this->selected,
];
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Filters;
use Minds\Core\Analytics\Dashboards\DashboardCollectionInteface;
class FiltersCollection implements DashboardCollectionInteface
{
/** @var FilterAbstract[] */
private $filters = [];
/** @var string[] */
private $selectedIds;
/**
* Set the selected metric id
* @param string[]
* @return self
*/
public function setSelectedIds(array $selectedIds): self
{
$this->selectedIds = $selectedIds;
return $this;
}
public function getSelected(): array
{
// Filters have scoped key pairs like
// key::value
// platform::browser
$selected = [];
foreach ($this->selectedIds as $selectedId) {
list($key, $value) = explode('::', $selectedId);
if (!isset($this->filters[$key])) {
continue;
}
$selected[$key] = $this->filters[$key];
$selected[$key]->selectOption($value);
}
return $selected;
}
/**
* Set the filters
* @param FilterAbstract[] $filters
* @return self
*/
public function addFilters(FilterAbstract ...$filters): self
{
foreach ($filters as $filter) {
$this->filters[$filter->getId()] = $filter;
}
return $this;
}
/**
* Return the set metrics
* @return FilterAbstract[]
*/
public function getFilters(): array
{
return $this->filters;
}
// public function build(): self
// {
// foreach ($this->filters as $filter) {
// $filter->build();
// }
// return $this;
// }
/**
* Export
* @param array $extras
* @return array
*/
public function export(array $extras = []): array
{
$export = [];
foreach ($this->filters as $filter) {
$export[] = $filter->export();
}
return $export;
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Filters;
class ViewsFilter extends FilterAbstract
{
/** @var string */
protected $id = "views";
/** @var string */
protected $label = "View types";
public function __construct()
{
$this->options = (new FilterOptions())
->setOptions(
(new FilterOptionsOption())
->setId("total")
->setLabel("Total"),
(new FilterOptionsOption())
->setId("organic")
->setLabel("Organic"),
(new FilterOptionsOption())
->setId("boosted")
->setLabel("Boosted"),
(new FilterOptionsOption())
->setId("single")
->setLabel("Single")
);
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics;
use Minds\Core\Di\Di;
class ActiveUsersMetric extends MetricAbstract
{
/** @var string */
protected $id = 'active_users';
/** @var string */
protected $label = 'active users';
/** @var array */
protected $permissions = [ 'admin' ];
/** @var MetricValues */
protected $values;
public function __construct($entityCentricManager = null)
{
$this->entityCentricManager = $entityCentricManager ?? Di::_()->get('Analytics\EntityCentric\Manager');
}
/**
* Build the metrics
* @return self
*/
public function build(): self
{
$timespan = $this->timespansCollection->getSelected();
$filters = $this->filtersCollection->getSelected();
$previousTs = strtotime('-1 ' . $timespan->getAggInterval(), $timespan->getFromTsMs() / 1000) * 1000;
$currentTs = $timespan->getFromTsMs();
$must = [];
// Range must be from previous period
$must[]['range'] = [
'@timestamp' => [
'gte' => $previousTs,
],
];
// Use our global metrics
$must[]['term'] = [
'entity_urn' => 'urn:metric:global'
];
// Field name to use for the aggregation
$aggField = "active::total";
// The aggregation type, this differs by resolution
$aggType = "sum";
// The resolution to use
$resolution = 'day';
switch ($timespan->getId()) {
case 'today':
$resolution = 'day';
$aggType = "sum";
break;
case 'mtd':
$resolution = 'month';
$aggType = "max";
break;
case 'ytd':
$resolution = 'month';
$aggType = "avg";
break;
}
// Specify the resolution to avoid duplicates
$must[] = [
'term' => [
'resolution' => $resolution,
],
];
$response = $this->entityCentricManager->getAggregateByQuery([
'query' => [
'bool' => [
'must' => $must,
],
],
'size' => 0,
'aggs' => [
'1' => [
'date_histogram' => [
'field' => '@timestamp',
'interval' => $timespan->getAggInterval(),
'min_doc_count' => 1,
],
'aggs' => [
'2' => [
$aggType => [
'field' => $aggField,
],
],
],
],
],
]);
$this->values = new MetricsValues();
$this->values->setCurrent($response[0]['1']['2']['value'])
->setPrevious($response[1]['1']['2']['value']);
return $this;
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics;
use Minds\Core\Analytics\Dashboards\Timespans\TimespansCollection;
use Minds\Core\Di\Di;
use Minds\Traits\MagicAttributes;
/**
* @method MetricAbstract setTimespansCollection(TimespansCollection $timespansCollection)
* @method string getId()
* @method string getLabel()
*/
abstract class MetricAbstract
{
use MagicAttributes;
/** @var string */
protected $id;
/** @var string */
protected $label;
/** @var string[] */
protected $permissions;
/** @var MetricValues */
protected $values;
/** @var TimespansCollection */
protected $timespansCollection;
/** @var FiltersCollection */
protected $filtersCollection;
/**
* Export
* @param array $extras
* @return array
*/
public function export($extras = []): array
{
return [
'id' => (string) $this->id,
'label' => (string) $this->label,
'permissions' => (array) $this->permissions,
'values' => $this->values ? (array) $this->values->export() : null,
];
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics;
use Minds\Traits\MagicAttributes;
class MetricValues
{
/** @var int */
private $current = 0;
/** @var int */
private $previous = 0;
/**
* Export
* @param array $extras
* @return array
*/
public function export(array $extras = []): array
{
return [
'current' => (int) $current,
'previous' => (int) $previous,
];
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Dashboards\Timespans\TimespansCollection;
use Minds\Core\Analytics\Dashboards\DashboardCollectionInteface;
class MetricsCollection implements DashboardCollectionInteface
{
/** @var MetricsAbstract[] */
private $metrics = [];
/** @var string */
private $selectedId;
/** @var TimespansCollection */
private $timespansCollection;
/** @var FiltersCollection */
private $filtersCollection;
/**
* @param TimespansCollection $timespansCollection
* @return self
*/
public function setTimespansCollection(TimespansCollection $timespansCollection): self
{
$this->timespansCollection = $timespansCollection;
return $this;
}
/**
* @param FiltersCollection $filtersCollection
* @return self
*/
public function setFiltersCollection(?FiltersCollection $filtersCollection): self
{
$this->filtersCollection = $filtersCollection;
return $this;
}
/**
* Set the selected metric id
* @param string
* @return self
*/
public function setSelectedId(string $selectedId): self
{
$this->selectedId = $selectedId;
return $this;
}
/**
* Set the metrics
* @param MetricAbstract[] $metric
* @return self
*/
public function addMetrics(MetricAbstract ...$metrics): self
{
foreach ($metrics as $metric) {
$this->metrics[$metric->getId()] = $metric;
}
return $this;
}
/**
* Return the set metrics
* @return MetricAbstract[]
*/
public function getMetrics(): array
{
return $this->metrics;
}
public function build(): self
{
foreach ($this->metrics as $metric) {
$metric
->setTimespansCollection($this->timespansCollection)
->build();
}
return $this;
}
/**
* Export
* @param array $extras
* @return array
*/
public function export(array $extras = []): array
{
$export = [];
foreach ($this->metrics as $metric) {
$export[] = $metric->export();
}
return $export;
}
}
<?php
namespace Minds\Core\Analytics\Dashboards\Metrics;
use Minds\Core\Di\Di;
class SignupsMetric extends MetricAbstract
{
/** @var string */
protected $id = 'signups';
/** @var string */
protected $label = 'signups';
/** @var array */
protected $permissions = [ 'admin' ];
/** @var MetricValues */
protected $values;
public function __construct($entityCentricManager = null)