...
 
Commits (2)
......@@ -21,6 +21,8 @@ class views implements Interfaces\Api
public function post($pages)
{
$viewsManager = new Core\Analytics\Views\Manager();
switch ($pages[0]) {
case 'boost':
$expire = Di::_()->get('Boost\Network\Expire');
......@@ -47,6 +49,18 @@ class views implements Interfaces\Api
Counters::increment($boost->getEntity()->guid, "impression");
Counters::increment($boost->getEntity()->owner_guid, "impression");
try {
// TODO: Ensure client_meta campaign matches this boost
$viewsManager->record(
(new Core\Analytics\Views\View())
->setEntityUrn($boost->getEntity()->getUrn())
->setClientMeta($_POST['client_meta'] ?? [])
);
} catch (\Exception $e) {
error_log($e);
}
return Factory::response([
'status' => 'success',
'impressions' => $boost->getImpressions(),
......@@ -88,6 +102,17 @@ class views implements Interfaces\Api
} catch (\Exception $e) {
error_log($e->getMessage());
}
try {
$viewsManager->record(
(new Core\Analytics\Views\View())
->setEntityUrn($activity->getUrn())
->setClientMeta($_POST['client_meta'] ?? [])
);
} catch (\Exception $e) {
error_log($e);
}
break;
}
......
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\Analytics\Views;
use Exception;
class Manager
{
/** @var Repository */
protected $repository;
public function __construct(
$repository = null
)
{
$this->repository = $repository ?: new Repository();
}
/**
* @param View $view
* @return bool
* @throws Exception
*/
public function record(View $view)
{
// Reset time fields and use current timestamp
$view
->setYear(null)
->setMonth(null)
->setDay(null)
->setUuid(null)
->setTimestamp(time());
// Add to repository
$this->repository->add($view);
return true;
}
}
<?php
/**
* Repository
* @author edgebal
*/
namespace Minds\Core\Analytics\Views;
use Cassandra\Rows;
use Cassandra\Timeuuid;
use Cassandra\Tinyint;
use DateTime;
use DateTimeZone;
use Exception;
use Minds\Common\Repository\Response;
use Minds\Core\Data\Cassandra\Client as CassandraClient;
use Minds\Core\Data\Cassandra\Prepared\Custom;
use Minds\Core\Di\Di;
class Repository
{
/** @var CassandraClient */
protected $db;
/**
* Repository constructor.
* @param CassandraClient $db
*/
public function __construct(
$db = null
)
{
$this->db = $db ?: Di::_()->get('Database\Cassandra\Cql');
}
/**
* @param array $opts
* @return Response
*/
public function getList(array $opts = [])
{
$opts = array_merge([
'limit' => 500,
'offset' => '',
], $opts) ;
$cql = "SELECT * FROM views";
$values = [];
$cqlOpts = [];
// TODO: Implement constraints (by year/month/day/timeuuid)
if ($opts['limit']) {
$cqlOpts['page_size'] = (int) $opts['limit'];
}
if ($opts['offset']) {
$cqlOpts['paging_state_token'] = base64_decode($opts['offset']);
}
$prepared = new Custom();
$prepared->query($cql, $values);
$prepared->setOpts($opts);
$response = new Response();
try {
/** @var Rows $rows */
$rows = $this->db->request($prepared);
foreach ($rows as $row) {
$view = new View();
$view
->setYear((int) $row['year'] ?: null)
->setMonth((int) $row['month'] ?: null)
->setDay((int) $row['day'] ?: null)
->setUuid($row['uuid']->uuid() ?: null)
->setEntityUrn($row['entity_urn'])
->setPageToken($row['page_token'])
->setPosition((int) $row['position'])
->setSource($row['platform'])
->setSource($row['source'])
->setMedium($row['medium'])
->setCampaign($row['campaign'])
->setDelta((int) $row['delta'])
->setTimestamp($row['uuid']->time());
$response[] = $view;
}
$response->setPagingToken(base64_encode($rows->pagingStateToken()));
$response->setLastPage($rows->isLastPage());
} catch (Exception $e) {
$response->setException($e);
}
return $response;
}
/**
* @param View $view
* @return bool
* @throws Exception
*/
public function add(View $view)
{
$timestamp = $view->getTimestamp() ?: time();
$date = new DateTime("@{$timestamp}", new DateTimeZone('utc'));
$cql = "INSERT INTO views (year, month, day, uuid, entity_urn, page_token, position, platform, source, medium, campaign, delta) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$values = [
(int) ($view->getYear() ?? $date->format('Y')),
new Tinyint((int) ($view->getMonth() ?? $date->format('m'))),
new Tinyint((int) ($view->getDay() ?? $date->format('d'))),
new Timeuuid($view->getUuid() ?? $timestamp * 1000),
$view->getEntityUrn() ?: '',
$view->getPageToken() ?: '',
(int) ($view->getPosition() ?? -1),
$view->getPlatform() ?: '',
$view->getSource() ?: '',
$view->getMedium() ?: '',
$view->getCampaign() ?: '',
(int) ($view->getDelta() ?? 0),
];
$prepared = new Custom();
$prepared->query($cql, $values);
try {
$this->db->request($prepared, true);
return true;
} catch (Exception $e) {
error_log($e);
return false;
}
}
}
<?php
/**
* View
* @author edgebal
*/
namespace Minds\Core\Analytics\Views;
use Minds\Traits\MagicAttributes;
/**
* Class View
* @package Minds\Core\Analytics\Views
* @method View setYear(int $year)
* @method int getYear()
* @method View setMonth(int $month)
* @method int getMonth()
* @method View setDay(int $year)
* @method int getDay()
* @method View setUuid(string $uuid)
* @method string getUuid()
* @method View setEntityUrn(string $entityUrn)
* @method string getEntityUrn()
* @method View setPageToken(string $pageToken)
* @method string getPageToken()
* @method View setPosition(int $position)
* @method int getPosition()
* @method View setPlatform(string $platform)
* @method string getPlatform()
* @method View setSource(string $source)
* @method string getSource()
* @method View setMedium(string $medium)
* @method string getMedium()
* @method View setCampaign(string $campaign)
* @method string getCampaign()
* @method View setDelta(int $delta)
* @method int getDelta()
* @method View setTimestamp(int $timestamp)
* @method int getTimestamp()
*/
class View
{
use MagicAttributes;
/** @var int */
protected $year;
/** @var int */
protected $month;
/** @var int */
protected $day;
/** @var string */
protected $uuid;
/** @var string */
protected $entityUrn;
/** @var string */
protected $pageToken;
/** @var int */
protected $position;
/** @var string */
protected $platform;
/** @var string */
protected $source;
/** @var string */
protected $medium;
/** @var string */
protected $campaign;
/** @var int */
protected $delta;
/** @var int */
protected $timestamp;
/**
* @param array $clientMeta
* @return $this
*/
public function setClientMeta(array $clientMeta)
{
$this->pageToken = $clientMeta['page_token'] ?? null;
$this->position = $clientMeta['position'] ?? null;
$this->platform = $clientMeta['platform'] ?? null;
$this->source = $clientMeta['source'] ?? null;
$this->medium = $clientMeta['medium'] ?? null;
$this->campaign = $clientMeta['campaign'] ?? null;
$this->delta = $clientMeta['delta'] ?? null;
return $this;
}
}
......@@ -1370,4 +1370,20 @@ CREATE TABLE minds.sendwyre_accounts (
user_guid varint,
sendwyre_account_id text,
PRIMARY KEY (user_guid)
);
\ No newline at end of file
);
CREATE TABLE minds.views (
year int,
month tinyint,
day tinyint,
uuid timeuuid,
entity_urn text,
page_token text,
position int,
platform text,
source text,
medium text,
campaign text,
delta int,
PRIMARY KEY (year, month, day, uuid, entity_urn, page_token)
);
<?php
namespace Spec\Minds\Core\Analytics\Views;
use Minds\Core\Analytics\Views\Manager;
use Minds\Core\Analytics\Views\Repository;
use Minds\Core\Analytics\Views\View;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
/** @var Repository */
protected $repository;
function let(
Repository $repository
)
{
$this->beConstructedWith($repository);
$this->repository = $repository;
}
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
function it_should_record(
View $view
)
{
$view->setYear(null)
->shouldBeCalled()
->willReturn($view);
$view->setMonth(null)
->shouldBeCalled()
->willReturn($view);
$view->setDay(null)
->shouldBeCalled()
->willReturn($view);
$view->setUuid(null)
->shouldBeCalled()
->willReturn($view);
$view->setTimestamp(Argument::type('int'))
->shouldBeCalled()
->willReturn($view);
$this->repository->add($view)
->shouldBeCalled()
->willReturn(true);
$this
->record($view)
->shouldReturn(true);
}
}
<?php
namespace Spec\Minds\Core\Analytics\Views;
use Minds\Common\Repository\Response;
use Minds\Core\Analytics\Views\Repository;
use Minds\Core\Analytics\Views\View;
use Minds\Core\Data\Cassandra\Client as CassandraClient;
use Minds\Core\Data\Cassandra\Prepared\Custom;
use PhpSpec\Exception\Example\FailureException;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
{
/** @var CassandraClient */
protected $db;
function let(
CassandraClient $db
)
{
$this->beConstructedWith($db);
$this->db = $db;
}
function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
}
// function it_should_get_list()
// {
// }
function it_should_add(
View $view
)
{
$now = strtotime('2019-05-29 12:00:00+0000');
$view->getTimestamp()
->shouldBeCalled()
->willReturn($now);
$view->getYear()
->shouldBeCalled()
->willReturn(2019);
$view->getMonth()
->shouldBeCalled()
->willReturn(5);
$view->getDay()
->shouldBeCalled()
->willReturn(29);
$view->getUuid()
->shouldBeCalled()
->willReturn('abc-123-456-def');
$view->getEntityUrn()
->shouldBeCalled()
->willReturn('urn:test:123123');
$view->getPageToken()
->shouldBeCalled()
->willReturn('1234-qwe-qwe-1234');
$view->getPosition()
->shouldBeCalled()
->willReturn(5);
$view->getPlatform()
->shouldBeCalled()
->willReturn('php');
$view->getSource()
->shouldBeCalled()
->willReturn('phpspec');
$view->getMedium()
->shouldBeCalled()
->willReturn('test');
$view->getCampaign()
->shouldBeCalled()
->willReturn('urn:phpspec:234234');
$view->getDelta()
->shouldBeCalled()
->willReturn(100);
$this->db->request(Argument::that(function (Custom $prepared) {
$statement = $prepared->build();
return stripos($statement['string'], 'insert into views') === 0 &&
$statement['values'][0] === 2019 &&
$statement['values'][1]->toInt() === 5 &&
$statement['values'][2]->toInt() === 29 &&
$statement['values'][3]->uuid() === 'abc-123-456-def' &&
$statement['values'][4] === 'urn:test:123123' &&
$statement['values'][5] === '1234-qwe-qwe-1234' &&
$statement['values'][6] === 5 &&
$statement['values'][7] === 'php' &&
$statement['values'][8] === 'phpspec' &&
$statement['values'][9] === 'test' &&
$statement['values'][10] === 'urn:phpspec:234234' &&
$statement['values'][11] === 100;
}), true)
->shouldBeCalled()
->willReturn(true);
$this
->add($view)
->shouldReturn(true);
}
function it_should_add_with_a_timestamp(
View $view
)
{
$now = strtotime('2019-05-29 12:00:00+0000');
$view->getTimestamp()
->shouldBeCalled()
->willReturn($now);
$view->getYear()
->shouldBeCalled()
->willReturn(null);
$view->getMonth()
->shouldBeCalled()
->willReturn(null);
$view->getDay()
->shouldBeCalled()
->willReturn(null);
$view->getUuid()
->shouldBeCalled()
->willReturn(null);
$view->getEntityUrn()
->shouldBeCalled()
->willReturn('urn:test:123123');
$view->getPageToken()
->shouldBeCalled()
->willReturn('1234-qwe-qwe-1234');
$view->getPosition()
->shouldBeCalled()
->willReturn(5);
$view->getPlatform()
->shouldBeCalled()
->willReturn('php');
$view->getSource()
->shouldBeCalled()
->willReturn('phpspec');
$view->getMedium()
->shouldBeCalled()
->willReturn('test');
$view->getCampaign()
->shouldBeCalled()
->willReturn('urn:phpspec:234234');
$view->getDelta()
->shouldBeCalled()
->willReturn(100);
$this->db->request(Argument::that(function (Custom $prepared) use ($now) {
$statement = $prepared->build();
return stripos($statement['string'], 'insert into views') === 0 &&
$statement['values'][0] === 2019 &&
$statement['values'][1]->toInt() === 5 &&
$statement['values'][2]->toInt() === 29 &&
$statement['values'][3]->time() === $now * 1000 &&
$statement['values'][4] === 'urn:test:123123' &&
$statement['values'][5] === '1234-qwe-qwe-1234' &&
$statement['values'][6] === 5 &&
$statement['values'][7] === 'php' &&
$statement['values'][8] === 'phpspec' &&
$statement['values'][9] === 'test' &&
$statement['values'][10] === 'urn:phpspec:234234' &&
$statement['values'][11] === 100;
}), true)
->shouldBeCalled()
->willReturn(true);
$this
->add($view)
->shouldReturn(true);
}
}
<?php
namespace Spec\Minds\Core\Analytics\Views;
use Minds\Core\Analytics\Views\View;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ViewSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(View::class);
}
function it_should_set_client_meta()
{
$this
->setClientMeta([
'page_token' => 'page_token_value',
'position' => 'position_value',
'platform' => 'platform_value',
'source' => 'source_value',
'medium' => 'medium_value',
'campaign' => 'campaign_value',
'delta' => 'delta_value',
])
->shouldReturn($this);
$this
->getPageToken()
->shouldReturn('page_token_value');
$this
->getPosition()
->shouldReturn('position_value');
$this
->getPlatform()
->shouldReturn('platform_value');
$this
->getSource()
->shouldReturn('source_value');
$this
->getMedium()
->shouldReturn('medium_value');
$this
->getCampaign()
->shouldReturn('campaign_value');
$this
->getDelta()
->shouldReturn('delta_value');
}
}
......@@ -297,6 +297,7 @@ if (!class_exists('Cassandra')) {
class_alias('MockSet', 'Cassandra\Set');
class_alias('MockMap', 'Cassandra\Map');
class_alias('Mock', 'Cassandra\Uuid');
class_alias('Mock', 'Cassandra\Timeuuid');
class_alias('Mock', 'Cassandra\Boolean');
class_alias('Mock', 'MongoDB\BSON\UTCDateTime');
class_alias('Mock', 'Cassandra\RetryPolicy\Logging');
......