Skip to content
Commits on Source (2)
......@@ -2,6 +2,7 @@
namespace Minds\Controllers\Cli;
use Elasticsearch\Common\Exceptions\ServerErrorResponseException;
use Minds\Cli;
use Minds\Core;
use Minds\Entities;
......@@ -18,15 +19,18 @@ class Analytics extends Cli\Controller implements Interfaces\CliControllerInterf
case 'sync_activeUsers':
$this->out('Indexes user activity by guid and counts per day');
$this->out('--from={timestamp} the day to start counting. Default is yesterday at midnight');
$$his->out('--to={timestamp} the day to stop counting. Default is yesterday at midnight');
$this->out('--to={timestamp} the day to stop counting. Default is yesterday at midnight');
$this->out('--rangeOffset={number of days} the number of days to look back into the past. Default is 7');
$this->out('--mode={silent | notify} silent mode does not send emails when running batches to re-index. Notify sends the notifications. Default is notify');
break;
case 'sync_graphs':
$this->out('sync graphs between es and cassandra');
break;
case 'counts':
$this->out('Prints the counts of a user');
$this->out('--from={timestamp in milliseconds} the day to start count. Default is yesterday');
$this->out('--guid={user guid} REQUIRED the user to aggregate');
// no break
// no break
default:
$this->out('Syntax usage: cli analytics <type>');
$this->displayCommandHelp();
......@@ -50,7 +54,7 @@ class Analytics extends Cli\Controller implements Interfaces\CliControllerInterf
$this->out('Collecting user activity');
$this->out("Running in {$mode} mode");
while ($from <= $to) {
$this->out('Syncing for '.gmdate('c', $from));
$this->out('Syncing for ' . gmdate('c', $from));
$manager = new Core\Analytics\UserStates\Manager();
$manager->setReferenceDate($from)
->setRangeOffset($rangeOffset)
......@@ -79,4 +83,51 @@ class Analytics extends Cli\Controller implements Interfaces\CliControllerInterf
var_dump($result);
}
public function sync_graphs()
{
error_reporting(E_ALL);
ini_set('display_errors', 1);
/** @var Core\Analytics\Graphs\Manager $manager */
$manager = Core\Di\Di::_()->get('Analytics\Graphs\Manager');
$aggregates = [
'avgpageviews',
// 'interactions',
'offchainboosts',
'onchainboosts',
'offchainplus',
'onchainplus',
'offchainwire',
'onchainwire',
'activeusers',
'posts',
'votes',
'comments',
'reminds',
//'subscribers',
'totalpageviews',
'usersegments',
'pageviews',
'withdraw',
'tokensales',
'rewards',
];
if ($this->getOpt('aggregate')) {
$aggregates = [ $this->getOpt('aggregate') ];
}
foreach ($aggregates as $aggregate) {
$this->out("Syncing {$aggregate}");
$manager->sync([
'aggregate' => $aggregate,
'all' => true,
]);
}
$this->out('Completed caching site metrics');
}
}
<?php
namespace Minds\Controllers\api\v2;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Interfaces;
class analytics implements Interfaces\Api, Interfaces\ApiIgnorePam
{
public function get($pages)
{
if (!isset($pages[0])) {
return Factory::response([
'status' => 'error',
'message' => 'report must be provided'
]);
}
$span = 12;
$unit = 'month';
switch ($_GET['timespan'] ?? null) {
case 'hourly':
$span = 24;
$unit = 'hour';
break;
case 'daily':
$span = 7;
$unit = 'day';
break;
case 'monthly':
$span = 12;
$unit = 'month';
break;
}
/** @var Core\Analytics\Metrics\Manager $manager */
$manager = Di::_()->get('Analytics\Graphs\Manager');
try {
$urn = "urn:graph:" . $manager::buildKey([
'aggregate' => $pages[0],
'key' => $_GET['key'] ?? null,
'span' => $span,
'unit' => $unit,
]);
$graph = $manager->get($urn);
if (!$graph) {
throw new \Exception("Graph not found");
}
$data = $graph->getData();
} catch (\Exception $e) {
error_log($e);
return Factory::response([
'status' => 'error',
'message' => $e->getMessage()
]);
}
return Factory::response([
'status' => 'success',
'data' => $data
]);
}
public function post($pages)
{
// TODO: Implement post() method.
}
public function put($pages)
{
// TODO: Implement put() method.
}
public function delete($pages)
{
// TODO: Implement delete() method.
}
}
......@@ -27,7 +27,7 @@ class pageview implements Interfaces\Api, Interfaces\ApiIgnorePam
]);
}
if(!isset($_COOKIE['mwa'])) {
if (!isset($_COOKIE['mwa'])) {
//@TODO make this more unique
$id = uniqid(true);
......
<?php
namespace Minds\Core\Analytics;
use Minds\Core\Analytics\Graphs;
use Minds\Core\Di\Provider;
class AnalyticsProvider extends Provider
{
public function register()
{
$this->di->bind('Analytics\Graphs\Manager', function ($di) {
return new Graphs\Manager();
}, ['useFactory' => true]);
$this->di->bind('Analytics\Graphs\Repository', function ($di) {
return new Graphs\Repository();
}, ['useFactory' => true]);
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class ActiveUsers implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['index'] : Di::_()->get('Config')->get('elasticsearch')['metrics_index'] . '-*';
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([ 'hour', 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'activeusers',
'key' => null,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'unit' => $unit,
]);
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'userGuid' => null,
], $options);
$user_guid = $options['userGuid'];
$from = null;
switch ($options['unit']) {
case "hour":
$from = (new DateTime('midnight'))->modify("-{$options['span']} hours");
$to = (new DateTime('midnight'));
$this->dateFormat = 'y-m-d H:i';
return $this->getHourlyPageviews($from, $to, $user_guid);
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$this->dateFormat = 'y-m-d';
return $this->getDailyPageviews($from, $to, $user_guid);
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$this->dateFormat = 'y-m';
return $this->getMonthlyPageviews($from, $to, $user_guid);
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
}
private function getHourlyPageviews($from, $to, $user_guid)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"platform.keyword" => [
"query" => "browser"
]
]
]
];
// filter by user_guid
if ($user_guid) {
$must[]['match'] = [
'entity_owner_guid.keyword' => $user_guid
];
}
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1h",
"min_doc_count" => 1
],
"aggs" => [
"hau_logged_in" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
],
"hau_unique" => [
"cardinality" => [
"field" => "cookie_id.keyword"
]
],
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'HAU (Logged In)',
'x' => [],
'y' => []
],
[
'name' => 'HAU (Unique)',
'x' => [],
'y' => []
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['hau_logged_in']['value'];
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['hau_unique']['value'];
}
return $response;
}
private function getDailyPageviews($from, $to, $user_guid)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"platform.keyword" => [
"query" => "browser"
]
]
]
];
// filter by user_guid
if ($user_guid) {
$must[]['match'] = [
'entity_owner_guid.keyword' => $user_guid
];
}
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1d",
"min_doc_count" => 1
],
"aggs" => [
"dau_logged_in" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
],
"dau_unique" => [
"cardinality" => [
"field" => "cookie_id.keyword"
]
],
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'DAU (Logged In)',
'x' => [],
'y' => []
],
[
'name' => 'DAU (Unique)',
'x' => [],
'y' => []
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['dau_logged_in']['value'];
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['dau_unique']['value'];
}
return $response;
}
private function getMonthlyPageviews($from, $to, $user_guid)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
];
// filter by user_guid
if ($user_guid) {
$must[]['match'] = [
'entity_owner_guid.keyword' => $user_guid
];
}
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1M",
"min_doc_count" => 1
],
"aggs" => [
"mau_logged_in" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
],
"mau_unique" => [
"cardinality" => [
"field" => "cookie_id.keyword"
]
],
"dau" => [
"avg_bucket" => [
"buckets_path" => "dau-bucket>dau-metric"
]
],
"dau-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1d",
"min_doc_count" => 1
],
"aggs" => [
"dau-metric" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
]
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'MAU',
'x' => [],
'y' => []
],
[
'name' => 'Visitors',
'x' => [],
'y' => []
],
[
'name' => 'Avg. DAU',
'x' => [],
'y' => []
],
[
'name' => 'DAU',
'x' => [],
'y' => []
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['mau_logged_in']['value'];
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['mau_unique']['value'];
$response[2]['x'][] = $date;
$response[2]['y'][] = $count['dau']['value'];
}
return $response;
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $options = [])
{
return "pageviews:{$options['unit']}:{$options['userGuid']}";
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
interface AggregateInterface
{
public function fetch(array $options = []);
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class AvgPageviews implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['index'] : Di::_()->get('Config')->get('elasticsearch')['metrics_index'] . '-*';
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([
'mau_unique',
'mau_loggedin',
'dau_loggedin',
'dau_unique',
'total_pageviews',
'hau_unique',
'hau_loggedin',
] as $key) {
foreach ([ 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'avgpageviews',
'key' => $key,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'key' => $key,
'unit' => $unit
]);
}
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'key' => null,
], $options);
if (!isset($options['key'])) {
throw new \Exception('key must be provided in the options array');
}
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
$response = null;
switch ($key) {
case 'mau_unique':
$response = $this->getMauUnique($from, $to, $interval);
break;
case 'mau_loggedin':
$response = $this->getMauLoggedIn($from, $to, $interval);
break;
case 'dau_loggedin':
$response = $this->getDauLoggedIn($from, $to);
break;
case 'dau_unique':
$response = $this->getDauUnique($from, $to);
break;
case 'total_pageviews':
$response = $this->getTotalPageviews($from, $to);
break;
case 'hau_unique':
$response = $this->getHauUnique($from, $to);
break;
case 'hau_loggedin':
$response = $this->getHauLoggedIn($from, $to);
break;
}
return $response;
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $opts = [])
{
return "{$opts['key']}:{$opts['unit']}";
}
private function getMauUnique($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "cookie_id.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getMauLoggedIn($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getDauLoggedIn($from, $to)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1d",
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getDauUnique($from, $to)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1d",
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "cookie_id.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getHauUnique($from, $to)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1h",
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "cookie_id.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getHauLoggedIn($from, $to)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1h",
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getTotalPageviews($from, $to)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"action.keyword" => [
"query" => "pageview"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>_count"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => "1M",
"min_doc_count" => 1
]
]
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
}
<?php
/**
* Amount of comments a user did in a given time period
*/
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Data\ElasticSearch\Prepared\Search;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class Comments implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['index'] : Di::_()->get('Config')->get('elasticsearch')['metrics_index'] . '-*';
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([ 'hour', 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'comments',
'key' => null,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'unit' => $unit,
]);
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'userGuid' => null,
], $options);
$userGuid = $options['userGuid'];
$from = null;
switch ($options['unit']) {
case "hour":
$from = (new DateTime('midnight'))->modify("-{$options['span']} hours");
$to = (new DateTime('midnight'));
$interval = '1h';
$this->dateFormat = 'y-m-d H:i';
break;
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
$result = null;
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => [
[
'match_all' => (object) []
],
[
'range' => [
'@timestamp' => [
'gte' => $from->getTimestamp() * 1000,
'lte' => $to->getTimestamp() * 1000,
'format' => 'epoch_millis'
]
]
],
[
"match_phrase" => [
"action.keyword" => [
"query" => "comment"
]
]
]
]
]
],
'aggs' => [
'comments' => [
'date_histogram' => [
'field' => '@timestamp',
'interval' => $interval,
'min_doc_count' => 1,
],
"aggs" => [
"uniques" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
]
]
]
]
]
];
if ($userGuid) {
$query['body']['query']['bool']['must'][] = [
'match' => [
'entity_owner_guid.keyword' => $userGuid
]
];
}
$prepared = new Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Comments',
'x' => [],
'y' => []
],
];
if (!$userGuid) {
$response[] = [
'name' => 'Number of Commenting Users',
'x' => [],
'y' => []
];
}
foreach ($result['aggregations']['comments']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['doc_count'];
if (!$userGuid) {
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['uniques']['value'];
}
}
return $response;
}
public function hasTTL(array $opts = [])
{
return isset($opts['userGuid']);
}
public function buildCacheKey(array $opts = [])
{
return "comments:{$opts['unit']}:{$opts['userGuid']}";
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Data\ElasticSearch\Prepared\Count;
use Minds\Core\Data\ElasticSearch\Prepared\Search;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class Interactions implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['index'] : Di::_()->get('Config')->get('elasticsearch')['metrics_index'] . '-*';
}
public function hasTTL(array $opts = [])
{
return true;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 1,
'unit' => 'day', // day / month
'key' => null,
'userGuid' => null,
], $options);
if (!isset($options['userGuid'])) {
throw new \Exception('userGuid must be set in the options parameter');
}
$userGuid = $options['userGuid'];
if (!isset($options['key'])) {
throw new \Exception('key must be set in the options parameter');
}
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
$result = null;
$boost = strpos($key, 'boost') !== false;
if (strpos($key, 'totals') !== false) {
$result = $this->getTotals($from, $to, $boost, $userGuid);
} else {
$result = $this->getMetrics($from, $to, $boost, $userGuid, $interval);
}
return $result;
}
public function buildCacheKey(array $options = [])
{
if (!isset($options['userGuid'])) {
throw new \Exception('userGuid must be set in the options parameter');
}
if (!isset($options['key'])) {
throw new \Exception('key must be set in the options parameter');
}
return "interactions:{$options['key']}:{$options['unit']}:{$options['userGuid']}";
}
private function getTotals($from, $to, $boost, $userGuid)
{
return [
[
'values' => [
$this->getTotal('vote:up', $from, $to, $boost, $userGuid),
$this->getTotal('vote:down', $from, $to, $boost, $userGuid),
$this->getTotal('comment', $from, $to, $boost, $userGuid),
$this->getTotal('remind', $from, $to, $boost, $userGuid),
],
'labels' => [
'Vote Up', 'Vote Down', 'Comment', 'Remind'
]
]
];
}
private function getTotal($action, $from, $to, $boost, $userGuid)
{
$query = [
'index' => $this->index,
'type' => 'action',
'body' => [
'query' => [
'bool' => [
'filter' => [
'term' => [
'action' => $action
]
],
'must_not' => [
'term' => [
'user_guid' => $userGuid // don't count self interactions
]
],
'must' => [
[
'match_all' => (object) []
],
[
'range' => [
'@timestamp' => [
'gte' => $from->getTimestamp() * 1000,
'lte' => $to->getTimestamp() * 1000,
'format' => 'epoch_millis'
]
]
],
[
'match' => [
'entity_owner_guid' => $userGuid
]
]
]
]
]
]
];
if ($boost) {
$query['body']['query']['bool']['must'][] = [
'exists' => [
'field' => 'client_meta_campaign'
]
];
}
$prepared = new Count();
$prepared->query($query);
$result = $this->client->request($prepared);
return $result['count'] ?? 0;
}
private function getMetrics($from, $to, $boost, $userGuid, $interval)
{
$query = [
'index' => $this->index,
'size' => 0,
'type' => 'action',
'body' => [
'query' => [
'bool' => [
'filter' => [
[
'terms' => [
'action' => ['vote:up', 'vote:down', 'comment', 'remind']
]
]
],
'must_not' => [
'term' => [
'user_guid' => $userGuid // don't count self interactions
]
],
'must' => [
[
'match_all' => (object) []
],
[
'range' => [
'@timestamp' => [
'gte' => $from->getTimestamp() * 1000,
'lte' => $to->getTimestamp() * 1000,
'format' => 'epoch_millis'
]
]
],
[
'match' => [
'entity_owner_guid' => $userGuid
]
]
]
]
],
'aggs' => [
'interactions' => [
'date_histogram' => [
'field' => '@timestamp',
'interval' => $interval,
'min_doc_count' => 1,
]
]
]
]
];
$prepared = new Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Interactions',
'x' => [],
'y' => []
]
];
foreach ($result['aggregations']['interactions']['buckets'] as $count) {
$response[0]['x'][] = date($this->dateFormat, $count['key'] / 1000);
$response[0]['y'][] = $count['doc_count'];
}
if ($boost) {
$query['body']['query']['bool']['must'][] = [
'exists' => [
'field' => 'client_meta_campaign'
]
];
$prepared = new Search();
$prepared->query($query);
$boostResult = $this->client->request($prepared);
$response[] = [
[
'name' => 'Boost Interactions',
'x' => [],
'y' => []
]
];
foreach ($boostResult['aggregations']['interactions']['buckets'] as $count) {
$response[1]['x'][] = date($this->dateFormat, $count['key'] / 1000);
$response[1]['y'][] = $count['doc_count'];
}
}
return $response;
}
}
This diff is collapsed.
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class OffchainPlus implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = 'minds-offchain*';
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $options = [])
{
return "offchain:plus:{$options['key']}:{$options['unit']}";
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([
'average_reclaimed_tokens',
'average_plus_users',
'average_plus_tx',
null,
] as $key) {
foreach ([ 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'offchainplus',
'key' => $key,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'key' => $key,
'unit' => $unit,
]);
}
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'key' => null,
], $options);
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
switch ($key) {
case 'average_reclaimed_tokens':
return $this->getAverageReclaimedTokens($from, $to, $interval);
break;
case 'average_plus_users':
return $this->getPlusUsers($from, $to, $interval);
break;
case 'average_plus_tx':
return $this->getAveragePlusTx($from, $to, $interval);
break;
default:
return $this->getGraph($from, $to, $interval);
}
}
private function getAverageReclaimedTokens($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"amount" => [
"query" => -20
]
]
],
[
"match" => [
"wire_receiver_guid" => "730071191229833224"
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"sum" => [
"field" => "amount",
"script" => [
"lang" => "expression",
"inline" => "doc['amount'] * multiplier",
"params" => [
"multiplier" => -1
]
]
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getPlusUsers($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match" => [
"wire_receiver_guid" => "730071191229833224"
]
],
[
"match_phrase" => [
"amount" => [
"query" => -20
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "wire_sender_guid"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAveragePlusTx($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"amount" => [
"query" => -20
]
]
],
[
"match" => [
"wire_receiver_guid" => "730071191229833224"
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>_count"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getGraph($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"amount" => [
"query" => -20
]
]
],
[
"match" => [
"wire_receiver_guid" => "730071191229833224"
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must,
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"amount" => [
"sum" => [
"field" => "amount",
"script" => [
"lang" => "expression",
"inline" => "doc['amount'] * multiplier",
"params" => [
"multiplier" => -1
]
]
]
],
"users" => [
"cardinality" => [
"field" => "wire_sender_guid"
]
]
]
]
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'OffChain Plus Tokens',
'x' => [],
'y' => [],
],
[
'name' => 'OffChain Plus Users',
'x' => [],
'y' => [],
],
[
'name' => 'OffChain Plus Transactions',
'x' => [],
'y' => [],
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['sums']['value'] ?? 0;
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['users']['value'] ?? 0;
$response[2]['x'][] = $date;
$response[2]['y'][] = $count['doc_count'] ?? 0;
}
return $response;
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class OffchainWire implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = 'minds-offchain*';
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $options = [])
{
return "offchain:wire:{$options['key']}:{$options['unit']}";
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([
'average',
'average_tokens',
'average_receivers',
'average_senders',
null,
] as $key) {
foreach ([ 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'offchainwire',
'key' => $key,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'key' => $key,
'unit' => $unit,
]);
}
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'key' => null,
], $options);
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
switch ($key) {
case 'average':
return $this->getAverage($from, $to, $interval);
break;
case 'average_tokens':
return $this->getAverageTokens($from, $to, $interval);
break;
case 'average_receivers':
return $this->getAverageReceivers($from, $to, $interval);
break;
case 'average_senders':
return $this->getAverageSenders($from, $to, $interval);
break;
default:
return $this->getGraph($from, $to, $interval);
}
}
private function getAverage($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"bool" => [
"must_not" => [
[
"term" => [
"wire_receiver_guid" => "730071191229833224"
]
]
]
]
],
[
"match_phrase" => [
"contract" => [
"query" => "offchain:wire"
]
]
],
[
"range" => [
"amount" => [
"gte" => 0,
"lt" => null
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>_count"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAverageTokens($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"bool" => [
"must_not" => [
[
"term" => [
"wire_receiver_guid" => "730071191229833224"
]
]
]
]
],
[
"match_phrase" => [
"contract" => [
"query" => "offchain:wire"
]
]
],
[
"range" => [
"amount" => [
"gte" => 0,
"lt" => null
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"sum" => [
"field" => "amount"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAverageReceivers($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"bool" => [
"must_not" => [
[
"term" => [
"wire_receiver_guid" => "730071191229833224"
]
]
]
]
],
[
"range" => [
"amount" => [
"gte" => 0,
"lt" => null
]
]
],
[
"match_phrase" => [
"contract" => [
"query" => "offchain:wire"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "wire_receiver_guid"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAverageSenders($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"bool" => [
"must_not" => [
[
"term" => [
"wire_receiver_guid" => "730071191229833224"
]
]
]
]
],
[
"range" => [
"amount" => [
"gte" => 0,
"lt" => null
]
]
],
[
"match_phrase" => [
"contract" => [
"query" => "offchain:wire"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "wire_sender_guid"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getGraph($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"bool"=> [
"must_not"=> [
[
"term"=> [
"wire_receiver_guid"=> "730071191229833224"
]
]
]
]
],
[
"match_phrase"=> [
"contract"=> [
"query"=> "offchain:wire"
]
]
],
[
"range"=> [
"amount"=> [
"gte"=> 0,
"lt"=> null
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must,
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"tokens" => [
"sum" => [
"field" => "amount"
]
],
"senders" => [
"cardinality" => [
"field" => "wire_sender_guid"
]
],
"receivers" => [
"cardinality" => [
"field" => "wire_receiver_guid"
]
]
]
]
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'OffChain Wire Transactions',
'x' => [],
'y' => [],
],
[
'name' => 'OffChain Wire Receivers',
'x' => [],
'y' => [],
],
[
'name' => 'OffChain Wire Senders',
'x' => [],
'y' => [],
],
[
'name' => 'OffChain Plus Tokens',
'x' => [],
'y' => [],
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['doc_count'] ?? 0;
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['receivers']['value'] ?? 0;
$response[2]['x'][] = $date;
$response[2]['y'][] = $count['senders']['value'] ?? 0;
$response[3]['x'][] = $date;
$response[3]['y'][] = $count['tokens']['value'] ?? 0;
}
return $response;
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class OnchainBoosts implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = 'minds-transactions-onchain*';
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $options = [])
{
return "onchain:boosts:{$options['key']}:{$options['unit']}";
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([
'average',
'average_reclaimed_tokens',
'average_users',
null,
] as $key) {
foreach ([ 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'onchainboosts',
'key' => $key,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'key' => $key,
'unit' => $unit,
]);
}
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'day', // day / month
'ignoreCache' => false,
'userGuid' => null,
'key' => null,
], $options);
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
switch ($key) {
case 'average':
return $this->getAvg($from, $to, $interval);
break;
case 'average_reclaimed_tokens':
return $this->getAvgReclaimedTokens($from, $to, $interval);
break;
case 'average_users':
return $this->getAvgUsers($from, $to, $interval);
break;
default: // no avg, show graph
return $this->getGraph($from, $to, $interval);
break;
}
}
private function getAvg($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "boost"
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>_count"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
return $result['aggregations']['avg']['value'] ?? 0;
}
private function getAvgReclaimedTokens($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "boost"
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"sum" => [
"field" => "tokenValue"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
return $result['aggregations']['avg']['value'] ?? 0;
}
private function getAvgUsers($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "boost"
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "from"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
return $result['aggregations']['avg']['value'] ?? 0;
}
private function getGraph($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "boost"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"sums" => [
"sum" => [
"field" => "tokenValue"
]
],
"unique" => [
"cardinality" => [
"field" => "from"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Reclaimed Tokens from OnChain Boosts',
'x' => [],
'y' => [],
],
[
'name' => 'Number of OnChain Boosts Transactions',
'x' => [],
'y' => [],
],
[
'name' => 'Number of Users that used OnChain Boosts',
'x' => [],
'y' => [],
],
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['sums']['value'] ?? 0;
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['doc_count'] ?? 0;
$response[2]['x'][] = $date;
$response[2]['y'][] = $count['unique']['value'] ?? 0;
}
return $response;
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class OnchainPlus implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = 'minds-transactions-onchain*';
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $options = [])
{
return "onchain:plus:{$options['key']}:{$options['unit']}";
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([
'average_reclaimed_tokens',
'average_plus_users',
'average_plus_tx',
] as $key) {
foreach ([ 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'onchainplus',
'key' => $key,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'key' => $key,
'unit' => $unit,
]);
}
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'key' => null,
], $options);
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
switch ($key) {
case 'average_reclaimed_tokens':
return $this->getAverageReclaimedTokens($from, $to, $interval);
break;
case 'average_plus_users':
return $this->getPlusUsers($from, $to, $interval);
break;
case 'average_plus_tx':
return $this->getAveragePlusTx($from, $to, $interval);
break;
default:
return $this->getGraph($from, $to, $interval);
}
}
private function getAverageReclaimedTokens($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "plus"
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"sum" => [
"field" => "tokenValue"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getPlusUsers($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "plus"
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "from"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAveragePlusTx($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "plus"
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>_count"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getGraph($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"amount" => [
"query" => -20
]
]
],
[
"match" => [
"wire_receiver_guid" => "730071191229833224"
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must,
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"users" => [
"cardinality" => [
"field" => "from"
]
],
"tokens" => [
"sum" => [
"field" => "tokenValue"
]
]
]
]
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'OnChain Plus Tokens',
'x' => [],
'y' => [],
],
[
'name' => 'OnChain Plus Users',
'x' => [],
'y' => [],
],
[
'name' => 'OnChain Plus Transactions',
'x' => [],
'y' => [],
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['tokens']['value'] ?? 0;
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['users']['value'] ?? 0;
$response[2]['x'][] = $date;
$response[2]['y'][] = $count['doc_count'] ?? 0;
}
return $response;
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class OnchainWire implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = 'minds-transactions-onchain*';
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $options = [])
{
return "onchain:wire:{$options['key']}:{$options['unit']}";
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([
'average',
'average_tokens',
'average_receivers',
'average_senders',
null,
] as $key) {
foreach ([ 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'offchainwire',
'key' => $key,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'key' => $key,
'unit' => $unit,
]);
}
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'key' => null,
], $options);
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
switch ($key) {
case 'average':
return $this->getAverage($from, $to, $interval);
break;
case 'average_tokens':
return $this->getAverageTokens($from, $to, $interval);
break;
case 'average_receivers':
return $this->getAverageReceivers($from, $to, $interval);
break;
case 'average_senders':
return $this->getAverageSenders($from, $to, $interval);
break;
default:
return $this->getGraph($from, $to, $interval);
}
}
private function getAverage($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "wire"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>_count"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAverageTokens($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "wire"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"sum" => [
"field" => "tokenValue"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAverageReceivers($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "wire"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "to"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getAverageSenders($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "wire"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"avg" => [
"avg_bucket" => [
"buckets_path" => "1-bucket>1-metric"
]
],
"1-bucket" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"1-metric" => [
"cardinality" => [
"field" => "from"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = $result['aggregations']['avg']['value'] ?? 0;
return $response;
}
private function getGraph($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"transactionCategory" => [
"query" => "wire"
]
]
],
[
"match_phrase" => [
"isTokenTransaction" => [
"query" => true
]
]
],
[
"match_phrase" => [
"function" => [
"query" => "approveAndCall"
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must,
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"senders" => [
"cardinality" => [
"field" => "from"
]
],
"receivers" => [
"cardinality" => [
"field" => "to"
]
],
"tokens" => [
"sum" => [
"field" => "tokenValue"
]
]
]
]
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'OnChain Wire Transactions',
'x' => [],
'y' => [],
],
[
'name' => 'OnChain Wire Receivers',
'x' => [],
'y' => [],
],
[
'name' => 'OnChain Wire Senders',
'x' => [],
'y' => [],
],
[
'name' => 'OnChain Plus Tokens',
'x' => [],
'y' => [],
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['doc_count'] ?? 0;
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['receivers']['value'] ?? 0;
$response[2]['x'][] = $date;
$response[2]['y'][] = $count['senders']['value'] ?? 0;
$response[3]['x'][] = $date;
$response[3]['y'][] = $count['tokens']['value'] ?? 0;
}
return $response;
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class Pageviews implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['index'] : Di::_()->get('Config')->get('elasticsearch')['metrics_index'] . '-*';
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([
null,
'routes',
] as $key) {
foreach ([ 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'pageviews',
'key' => $key,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'key' => $key,
'unit' => $unit,
]);
}
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'key' => null,
], $options);
$key = $options['key'];
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
$response = null;
if ($key && $key == 'routes') {
$response = $this->getRoutes($from, $to, $interval);
} else {
$response = $this->getGraph($from, $to, $interval);
}
return $response;
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $opts = [])
{
return "pageviews:{$opts['key']}:{$opts['unit']}";
}
private function getRoutes($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
'match_phrase' => [
'action.keyword' => [
'query' => 'pageview'
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"routes" => [
"terms" => [
"field" => "route_uri.keyword",
'size' => 9,
'order' => [
'_count' => 'desc'
]
]
],
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Pageviews by Route',
'values' => [],
'labels' => []
]
];
foreach ($result['aggregations']['routes']['buckets'] as $count) {
$response[0]['labels'][] = $count['key'];
$response[0]['values'][] = $count['doc_count'];
}
return $response;
}
private function getGraph($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
'match_phrase' => [
'action.keyword' => [
'query' => 'pageview'
]
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
'interval' => $interval,
'min_doc_count' => 1
]
],
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Number of Pageviews',
'x' => [],
'y' => []
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$response[0]['x'][] = date($this->dateFormat, $count['key'] / 1000);
$response[0]['y'][] = $count['doc_count'];
}
return $response;
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class Posts implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['index'] : Di::_()->get('Config')->get('elasticsearch')['index'];
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([ 'hour', 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'posts',
'key' => null,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'unit' => $unit,
]);
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'userGuid' => null,
], $options);
$userGuid = $options['userGuid'];
$from = null;
switch ($options['unit']) {
case "hour":
$from = (new DateTime('midnight'))->modify("-{$options['span']} hours");
$to = (new DateTime('midnight'));
$interval = '1h';
$this->dateFormat = 'y-m-d H:i';
break;
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"_type" => [
"query" => "activity"
]
]
]
];
// filter by user_guid
if ($userGuid) {
$must[]['match'] = [
'entity_owner_guid.keyword' => $userGuid
];
}
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"posts" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"unique" => [
"cardinality" => [
"field" => "owner_guid.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Posts',
'x' => [],
'y' => []
]
];
if (!$userGuid) {
$response[] = [
'name' => 'Number of Posting Users',
'x' => [],
'y' => []
];
}
foreach ($result['aggregations']['posts']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['doc_count'];
if (!$userGuid) {
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['unique']['value'];
}
}
return $response;
}
public function hasTTL(array $opts = [])
{
return isset($opts['userGuid']);
}
public function buildCacheKey(array $options = [])
{
return "posts:{$options['unit']}:{$options['userGuid']}";
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class Reminds implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['metrics_index'] : Di::_()->get('Config')->get('elasticsearch')['metrics_index'] . '-*';
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([ 'hour', 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'reminds',
'key' => null,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'unit' => $unit,
]);
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'userGuid' => null,
], $options);
$userGuid = $options['userGuid'];
$from = null;
switch ($options['unit']) {
case "hour":
$from = (new DateTime('midnight'))->modify("-{$options['span']} hours");
$to = (new DateTime('midnight'));
$interval = '1h';
$this->dateFormat = 'y-m-d H:i';
break;
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"match_phrase" => [
"action.keyword" => [
"query" => "remind"
]
]
]
];
// filter by user_guid
if ($userGuid) {
$must[]['match'] = [
'entity_owner_guid.keyword' => $userGuid
];
}
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must
]
],
"aggs" => [
"reminds" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"unique" => [
"cardinality" => [
"field" => "user_guid.keyword"
]
]
]
]
],
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Reminds',
'x' => [],
'y' => []
]
];
if (!$userGuid) {
$response[] = [
'name' => 'Number of Reminding Users',
'x' => [],
'y' => []
];
}
foreach ($result['aggregations']['reminds']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['doc_count'];
if (!$userGuid) {
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['unique']['value'];
}
}
return $response;
}
public function hasTTL(array $opts = [])
{
return isset($opts['userGuid']);
}
public function buildCacheKey(array $options = [])
{
return "reminds:{$options['unit']}:{$options['userGuid']}";
}
}
<?php
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Di\Di;
use Minds\Interfaces\AnalyticsMetric;
use Minds\Core\Analytics\Graphs\Manager;
class Rewards implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = 'minds-offchain*';
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([ 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'rewards',
'key' => null,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'unit' => $unit,
]);
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
], $options);
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
return $this->getGraph($from, $to, $interval);
}
private function getGraph($from, $to, $interval)
{
$must = [
[
"match_all" => (object) []
],
[
"range" => [
"@timestamp" => [
"gte" => $from->getTimestamp() * 1000,
"lte" => $to->getTimestamp() * 1000,
"format" => "epoch_millis"
]
]
],
[
"range" => [
"amount" => [
"gte" => 0,
"lt" => null
]
]
],
[
"bool" => [
"should" => [
[
"match_phrase" => [
"contract" => "offchain:reward"
]
],
[
"match_phrase" => [
"contract" => "offchain:joined"
]
]
],
"minimum_should_match" => 1
]
]
];
$query = [
'index' => $this->index,
'size' => 0,
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'must' => $must,
]
],
"aggs" => [
"histogram" => [
"date_histogram" => [
"field" => "@timestamp",
"interval" => $interval,
"min_doc_count" => 1
],
"aggs" => [
"count" => [
"terms" => [
"field" => "contract",
"size" => 5,
"order" => [
"_count" => "desc"
]
],
"aggs" => [
"sums" => [
"sum" => [
"field" => "amount"
]
]
]
]
]
]
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Reward Transactions',
'x' => [],
'y' => [],
],
[
'name' => 'Rewarded Tokens',
'x' => [],
'y' => [],
]
];
foreach ($result['aggregations']['histogram']['buckets'] as $count) {
$date = date($this->dateFormat, $count['key'] / 1000);
$response[0]['x'][] = $date;
$response[0]['y'][] = $count['count']['buckets'][0]['sums']['value'] ?? 0;
$response[1]['x'][] = $date;
$response[1]['y'][] = $count['count']['buckets'][0]['doc_count'] ?? 0;
}
return $response;
}
public function hasTTL(array $opts = [])
{
return false;
}
public function buildCacheKey(array $options = [])
{
return "rewards:{$options['unit']}";
}
}
<?php
/**
* Amount of new subscribers a given user had per month
*/
namespace Minds\Core\Analytics\Graphs\Aggregates;
use DateTime;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Data\ElasticSearch\Prepared\Search;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\Graphs\Manager;
class Subscribers implements AggregateInterface
{
/** @var Client */
protected $client;
/** @var abstractCacher */
protected $cacher;
/** @var string */
protected $index;
/** @var string */
protected $dateFormat;
public function __construct($client = null, $cacher = null, $config = null)
{
$this->client = $client ?: Di::_()->get('Database\ElasticSearch');
$this->cacher = $cacher ?: Di::_()->get('Cache\Redis');
$this->index = $config ? $config->get('elasticsearch')['index'] : Di::_()->get('Config')->get('elasticsearch')['metrics_index'] . '-*';
}
/**
* Fetch all
* @param array $opts
* @return array
*/
public function fetchAll($opts = [])
{
$result = [];
foreach ([ 'hour', 'day', 'month' ] as $unit) {
$k = Manager::buildKey([
'aggregate' => $opts['aggregate'] ?? 'subscribers',
'key' => null,
'unit' => $unit,
]);
$result[$k] = $this->fetch([
'unit' => $unit,
]);
}
return $result;
}
public function fetch(array $options = [])
{
$options = array_merge([
'span' => 12,
'unit' => 'month', // day / month
'userGuid' => null,
], $options);
if (!isset($options['userGuid'])) {
throw new \Exception('userGuid must be set in the options parameter');
}
$userGuid = $options['userGuid'];
$from = null;
switch ($options['unit']) {
case "day":
$from = (new DateTime('midnight'))->modify("-{$options['span']} days");
$to = (new DateTime('midnight'));
$interval = '1d';
$this->dateFormat = 'y-m-d';
break;
case "month":
$from = (new DateTime('midnight first day of next month'))->modify("-{$options['span']} months");
$to = new DateTime('midnight first day of next month');
$interval = '1M';
$this->dateFormat = 'y-m';
break;
default:
throw new \Exception("{$options['unit']} is not an accepted unit");
}
$query = [
'index' => 'minds-metrics-*',
'size' => 0,
'type' => 'action',
"stored_fields" => [
"*"
],
"docvalue_fields" => [
(object) [
"field" => "@timestamp",
"format" => "date_time"
]
],
'body' => [
'query' => [
'bool' => [
'filter' => [
'term' => [
'action' => 'subscribe'
]
],
'must' => [
[
'match_all' => (object) []
],
[
'range' => [
'@timestamp' => [
'gte' => $from->getTimestamp() * 1000,
'lte' => $to->getTimestamp() * 1000,
'format' => 'epoch_millis'
]
]
],
[
'match' => [
'entity_guid' => $userGuid
]
]
]
]
],
'aggs' => [
'subscribers' => [
'date_histogram' => [
'field' => '@timestamp',
'interval' => $interval,
'min_doc_count' => 1,
],
'aggs' => [
'uniques' => [
'cardinality' => [
'field' => 'user_guid.keyword',
'precision_threshold' => 40000
]
]
]
]
]
]
];
$prepared = new Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$response = [
[
'name' => 'Subscribers',
'x' => [],
'y' => [],
]
];
foreach ($result['aggregations']['subscribers']['buckets'] as $count) {
$response[0]['x'][] = date($this->dateFormat, $count['key'] / 1000);
$response[0]['y'][] = $count['doc_count'];
}
return $response;
}
public function hasTTL(array $opts = [])
{
return true;
}
public function buildCacheKey(array $options = [])
{
if (!isset($options['userGuid'])) {
throw new \Exception('userGuid must be set in the options parameter');
}
$userGuid = $options['userGuid'];
return "subscribers:{$userGuid}:{$options['unit']}";
}
}