...
 
Commits (6)
<?php
namespace Minds\Core\Analytics\Delegates;
use Minds\Core\Analytics\UserStates\UserState;
use Minds\Core\Events\Dispatcher;
use Minds\Core\Notification\Notification;
use Minds\Entities\User;
class UpdateUserState
{
/**
* @var User
*/
private $user;
/**
* @var UserState
*/
private $userState;
public function __construct(UserState $userState, User $user = null)
{
$this->userState = $userState;
$this->user = $user ?? new User($userState->getUserGuid());
}
public function update(): void
{
if ($this->user->getUserState() !== $this->userState->getState()) {
$this->updateUserEntity();
$this->sendStateChangeNotification();
}
}
private function updateUserEntity(): void
{
$this->user->setUserState($this->userState->getState())
->setUserStateUpdatedMs($this->userState->getReferenceDateMs())
->save();
}
private function sendStateChangeNotification(): void
{
Dispatcher::trigger('notification', 'reward', [
'to'=> [
$this->userState->getUserGuid()
],
'from' => Notification::SYSTEM_ENTITY,
'notification_view' => 'rewards_state_change',
'params' => $this->userState->export()
]);
}
}
......@@ -19,13 +19,7 @@ class Events
{
$this->eventsDispatcher->register('user_state_change', 'all', function (Core\Events\Event $event) {
$userState = Core\Analytics\UserStates\UserState::fromArray($event->getParameters());
$user = new \Minds\Entities\User($userState->getUserGuid());
if ($user->getUserState() !== $userState->getState()) {
$user->setUserState($userState->getState())
->setUserStateUpdatedMs($userState->getReferenceDateMs())
->save();
}
(new Core\Analytics\Delegates\UpdateUserState($userState))->update();
});
}
}
<?php
namespace Minds\Core\Analytics\UserStates;
class RewardFactor
{
static $values = [
UserState::STATE_CASUAL => 1.5,
UserState::STATE_COLD => 0.5,
UserState::STATE_CORE => 0.5,
UserState::STATE_CURIOUS => 1,
UserState::STATE_NEW => 2,
UserState::STATE_RESURRECTED => 1.5,
UserState::STATE_UNKNOWN => 1
];
public static function getForUserState(?string $userState): float
{
return static::$values[$userState] ?? static::$values[UserState::STATE_UNKNOWN];
}
}
......@@ -30,14 +30,6 @@ class UserActivityBuckets
{
use MagicAttributes;
const STATE_CASUAL = 'casual';
const STATE_COLD = 'cold';
const STATE_CORE = 'core';
const STATE_CURIOUS = 'curious';
const STATE_NEW = 'new';
const STATE_RESURRECTED = 'resurrected';
const STATE_UNKNOWN = 'unknown';
const THRESHOLD_CASUAL_USER = .25;
const THRESHOLD_CORE_USER = .75;
const NEW_USER_AGE_HOURS = 24;
......@@ -103,19 +95,19 @@ class UserActivityBuckets
public function getState() : string
{
if ($this->isNewUser()) {
return static::STATE_NEW;
return UserState::STATE_NEW;
} elseif ($this->getActivityPercentage() >= static::THRESHOLD_CORE_USER) {
return static::STATE_CORE;
return UserState::STATE_CORE;
} elseif ($this->getActivityPercentage() >= static::THRESHOLD_CASUAL_USER) {
return static::STATE_CASUAL;
return UserState::STATE_CASUAL;
} elseif ($this->mostRecentDayCount > 0 && $this->getActiveDayCount() == 1) {
return static::STATE_RESURRECTED;
return UserState::STATE_RESURRECTED;
} elseif ($this->oldestDayCount > 0 && $this->getActiveDayCount() == 0) {
return static::STATE_COLD;
return UserState::STATE_COLD;
} elseif ($this->getActiveDayCount() >= 1) {
return static::STATE_CURIOUS;
return UserState::STATE_CURIOUS;
}
return static::STATE_UNKNOWN;
return UserState::STATE_UNKNOWN;
}
}
......@@ -25,6 +25,14 @@ use Minds\Traits\MagicAttributes;
*/
class UserState
{
const STATE_CASUAL = 'casual';
const STATE_COLD = 'cold';
const STATE_CORE = 'core';
const STATE_CURIOUS = 'curious';
const STATE_NEW = 'new';
const STATE_RESURRECTED = 'resurrected';
const STATE_UNKNOWN = 'unknown';
use MagicAttributes;
/** @var int $userGuid */
......@@ -50,6 +58,8 @@ class UserState
'state' => $this->state,
'previous_state' => $this->previousState,
'activity_percentage' => $this->activityPercentage,
'reward_factor' => RewardFactor::getForUserState($this->state),
'previous_reward_factor' => RewardFactor::getForUserState($this->previousState),
];
}
......
......@@ -5,15 +5,10 @@
namespace Minds\Core\Email;
use Minds\Core\Di\Di;
use Minds\Core\Analytics\UserStates\UserState;
use Minds\Core\Events\Dispatcher;
use Minds\Core\Analytics\UserStates\UserActivityBuckets;
use Minds\Core\Email\Campaigns\UserRetention\WelcomeComplete;
use Minds\Core\Email\Campaigns\UserRetention\WelcomeIncomplete;
use Minds\Core\Events\Event;
use Minds\Entities\User;
use Minds\Core\Email\Manager;
use Minds\Core\Suggestions\Manager as SuggestionManager;
use Minds\Interfaces\SenderInterface;
class Events
......@@ -24,33 +19,33 @@ class Events
error_log('user_state_change all');
});
Dispatcher::register('user_state_change', UserActivityBuckets::STATE_CASUAL, function ($opts) {
Dispatcher::register('user_state_change', UserState::STATE_CASUAL, function ($opts) {
error_log('user_state_change casual');
});
Dispatcher::register('user_state_change', UserActivityBuckets::STATE_CORE, function ($opts) {
Dispatcher::register('user_state_change', UserState::STATE_CORE, function ($opts) {
error_log('user_state_change core');
});
Dispatcher::register('user_state_change', UserActivityBuckets::STATE_CURIOUS, function ($opts) {
Dispatcher::register('user_state_change', UserState::STATE_CURIOUS, function ($opts) {
error_log('user_state_change curious');
});
Dispatcher::register('user_state_change', UserActivityBuckets::STATE_NEW, function ($opts) {
Dispatcher::register('user_state_change', UserState::STATE_NEW, function (Event $event) {
error_log('user_state_change new');
$this->sendCampaign(new Delegates\WelcomeSender(), $opts->getParameters());
$this->sendCampaign(new Delegates\WelcomeSender(), $event->getParameters());
});
Dispatcher::register('user_state_change', UserActivityBuckets::STATE_RESURRECTED, function ($opts) {
Dispatcher::register('user_state_change', UserState::STATE_RESURRECTED, function ($opts) {
error_log('user_state_change resurrected');
});
Dispatcher::register('user_state_change', UserActivityBuckets::STATE_COLD, function ($opts) {
$this->sendCampaign(new Delegates\GoneColdSender(), $opts->getParameters());
Dispatcher::register('user_state_change', UserState::STATE_COLD, function (Event $event) {
$this->sendCampaign(new Delegates\GoneColdSender(), $event->getParameters());
});
Dispatcher::register('welcome_email', 'all', function ($opts) {
$this->sendCampaign(new Delegates\WelcomeSender(), $opts->getParameters());
Dispatcher::register('welcome_email', 'all', function (Event $event) {
$this->sendCampaign(new Delegates\WelcomeSender(), $event->getParameters());
});
}
......
......@@ -22,10 +22,11 @@ class Dispatcher
/**
* Register a handler for an event.
* @param type $event The event
* @param type $namespace Namespace for this event (e.g. object type)
* @param string $event The event
* @param string $namespace Namespace for this event (e.g. object type)
* @param \callable $handler a callable handler
* @param type $priority Priority - lower numbers executed first.
* @param int $priority Priority - lower numbers executed first.
* @return bool
*/
public static function register($event, $namespace, $handler, $priority = 500)
{
......
......@@ -93,7 +93,7 @@ class Events
$notification
]);
});
});
/**
* Create a notification upon @mentioning on activities or comments
......
......@@ -32,6 +32,8 @@ use Minds\Traits\MagicAttributes;
*/
class Notification
{
const SYSTEM_ENTITY = 100000000000000519;
use MagicAttributes;
/** @param string $uuid */
......
......@@ -53,7 +53,7 @@ class User extends \ElggUser
$this->attributes['canary'] = 0;
$this->attributes['onchain_booster'] = null;
$this->attributes['toaster_notifications'] = 1;
$this->attributes['user_state'] = Core\Analytics\UserStates\UserActivityBuckets::STATE_UNKNOWN;
$this->attributes['user_state'] = Core\Analytics\UserStates\UserState::STATE_UNKNOWN;
$this->attributes['user_state_updated_ms'] = 0;
parent::initializeAttributes();
......
<?php
namespace Spec\Minds\Core\Analytics\Delegates;
use Minds\Core\Analytics\Delegates\UpdateUserState;
use Minds\Core\Analytics\UserStates\UserState;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
class UpdateUserStateSpec extends ObjectBehavior
{
public function let(UserState $userState, User $user)
{
$this->beConstructedWith($userState, $user);
}
public function it_is_initializable()
{
$this->shouldHaveType(UpdateUserState::class);
}
public function it_should_update_user_if_activity_state_change(UserState $userState, User $user)
{
$user->getUserState()->shouldBeCalled()->willReturn(UserState::STATE_UNKNOWN);
$userState->getState()->shouldBeCalled()->willReturn(UserState::STATE_NEW);
$userState->getReferenceDateMs()->shouldBeCalled()->willReturn(1563753600000);
$userState->getUserGuid()->shouldBeCalled()->willReturn(1001);
$userState->export()->shouldBeCalled()->willReturn([]);
$user->setUserState(UserState::STATE_NEW)->shouldBeCalled()->willReturn($user);
$user->setUserStateUpdatedMs(1563753600000)->shouldBeCalled()->willReturn($user);
$user->save()->shouldBeCalled();
$this->update();
}
public function it_should_not_update_user_if_no_activity_state_change(UserState $userState, User $user)
{
$user->getUserState()->shouldBeCalled()->willReturn(UserState::STATE_NEW);
$userState->getState()->shouldBeCalled()->willReturn(UserState::STATE_NEW);
$user->save()->shouldNotBeCalled();
$this->update();
}
}
......@@ -306,11 +306,14 @@ if (!class_exists('Cassandra')) {
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');
class_alias('Mock', 'Cassandra\RetryPolicy\DowngradingConsistency');
}
if (!class_exists('MongoDB\BSON\UTCDateTime')) {
class_alias('Mock', 'MongoDB\BSON\UTCDateTime');
}
Minds\Core\Di\Di::_()->bind('Database\Cassandra\Cql', function($di) {
return new Mock;
});