...
 
Commits (7)
......@@ -5,7 +5,7 @@ namespace Minds\Controllers\Cli;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Cli;
use Minds\Core\Reports\Summons\Summon;
use Minds\Core\Reports\Summons\Summons;
use Minds\Interfaces;
use Minds\Entities;
......@@ -83,16 +83,16 @@ class Moderation extends Cli\Controller implements Interfaces\CliControllerInter
$this->out("Summoned {$user->guid} to {$reportUrn}");
} else {
$summon = new Summon();
$summon
$summons = new Summons();
$summons
->setReportUrn($reportUrn)
->setJuryType($juryType)
->setJurorGuid((string) $user->guid)
->setStatus($respond);
$summonsManager->respond($summon);
$summonsManager->respond($summons);
$this->out("Responded to {$user->guid}'s summon to {$reportUrn} with {$respond}");
$this->out("Responded to {$user->guid}'s summons to {$reportUrn} with {$respond}");
}
}
......
......@@ -9,9 +9,9 @@ namespace Minds\Controllers\api\v2\moderation;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Reports\Repository as ReportsRepository;
use Minds\Core\Reports\Manager as ReportsManager;
use Minds\Core\Reports\Summons\Manager;
use Minds\Core\Reports\Summons\Summon as SummonEntity;
use Minds\Core\Reports\Summons\Summons as SummonsEntity;
use Minds\Core\Session;
use Minds\Interfaces;
......@@ -46,15 +46,15 @@ class summons implements Interfaces\Api
/** @var ReportsManager $reportsManager */
$reportsManager = Di::_()->get('Moderation\Manager');
$summon = new SummonEntity();
$summons = new SummonsEntity();
try {
$summon
$summons
->setReportUrn($reportUrn)
->setJuryType($juryType)
->setJurorGuid((string) $userGuid)
->setStatus($status);
$summonsManager->respond($summon);
$summonsManager->respond($summons);
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
......@@ -63,13 +63,13 @@ class summons implements Interfaces\Api
}
$response = [
'summon' => $summon->getStatus(),
'expires_in' => $summon->getTtl(),
'summon' => $summons->getStatus(),
'expires_in' => $summons->getTtl(),
];
if ($summon->isAccepted()) {
if ($summons->isAccepted()) {
$response['report'] = $reportsManager
->getReport($summon->getReportUrn())
->getReport($summons->getReportUrn())
->export();
}
......
......@@ -15,7 +15,7 @@ use Minds\Common\Repository\Response;
use Minds\Common\Urn;
use Minds\Core\Entities\Resolver as EntitiesResolver;
use Minds\Core\Reports\Summons\SummonsNotFoundException;
use Minds\Core\Reports\Summons\Summon as SummonsEntity;
use Minds\Core\Reports\Summons\Summons as SummonsEntity;
use Minds\Core\Security\ACL;
class Manager
......
......@@ -17,18 +17,30 @@ class Cohort
/** @var Pool */
protected $pool;
/** @var int */
protected $poolSize;
/** @var int */
protected $maxPages;
/**
* Cohort constructor.
* @param Repository $repository
* @param Pool $pool
* @param int $poolSize
* @param int $maxPages
*/
public function __construct(
$repository = null,
$pool = null
$pool = null,
$poolSize = null,
$maxPages = null
)
{
$this->repository = $repository ?: new Repository();
$this->pool = $pool ?: new Pool();
$this->poolSize = $poolSize ?: 400;
$this->maxPages = $maxPages ?: 1; // NOTE: Normally capped to 20.
}
/**
......@@ -47,16 +59,10 @@ class Cohort
$cohort = [];
// Uncomment below to scale
// $poolSize = $opts['size'] * 5;
// $max_pages = 20; // NOTE: Normally capped to 20.
$poolSize = 400;
$max_pages = 1; // NOTE: Normally capped to 20.
$page = 0;
while (true) {
if ($page > $max_pages) {
if ($page > $this->maxPages) {
// Max = PoolSize * MaxPages
error_log('Cannot gather a cohort');
break;
......@@ -67,10 +73,10 @@ class Cohort
'platform' => 'browser',
'for' => $opts['for'],
'except' => $opts['except'],
'validated' => false,
'size' => $poolSize,
'validated' => true,
'page' => $page,
'max_pages' => $max_pages,
'size' => $this->poolSize,
'max_pages' => $this->maxPages,
]);
$j = 0;
......
......@@ -8,7 +8,7 @@
namespace Minds\Core\Reports\Summons\Delegates;
use Exception;
use Minds\Core\Reports\Summons\Summon;
use Minds\Core\Reports\Summons\Summons;
use Minds\Core\Sockets\Events as SocketEvents;
class SocketDelegate
......@@ -28,13 +28,13 @@ class SocketDelegate
}
/**
* @param Summon $summon
* @param Summons $summons
* @throws Exception
*/
public function onSummon(Summon $summon)
public function onSummon(Summons $summons)
{
$this->socketEvents
->setUser($summon->getJurorGuid())
->emit('moderation_summon', json_encode($summon));
->setUser($summons->getJurorGuid())
->emit('moderation_summon', json_encode($summons));
}
}
......@@ -82,13 +82,13 @@ class Manager
// Remove the summons of jurors who have already voted
$summonses = array_filter($summonses, function (Summon $summons) use ($completedJurorGuids) {
$summonses = array_filter($summonses, function (Summons $summons) use ($completedJurorGuids) {
return !in_array($summons->getJurorGuid(), $completedJurorGuids);
});
// Check how many are missing
$notDeclined = array_filter($summonses, function (Summon $summons) {
$notDeclined = array_filter($summonses, function (Summons $summons) {
return $summons->isAccepted() || $summons->isAwaiting();
});
......@@ -102,7 +102,7 @@ class Manager
// Reduce jury to juror guids and try to pick up to missing size
$pendingJurorGuids = array_map(function (Summon $summons) {
$pendingJurorGuids = array_map(function (Summons $summons) {
return (string) $summons->getJurorGuid();
}, $summonses);
......@@ -115,47 +115,47 @@ class Manager
}
foreach ($cohort as $juror) {
$summon = new Summon();
$summon
$summons = new Summons();
$summons
->setReportUrn($reportUrn)
->setJuryType($juryType)
->setJurorGuid($juror)
->setTtl(120)
->setStatus('awaiting');
$this->repository->add($summon);
$this->socketDelegate->onSummon($summon);
$this->repository->add($summons);
$this->socketDelegate->onSummon($summons);
}
return $missing;
}
/**
* @param Summon $summon
* @param Summons $summons
* @return bool
*/
public function isSummoned(Summon $summon)
public function isSummoned(Summons $summons)
{
return $this->repository->exists($summon);
return $this->repository->exists($summons);
}
/**
* @param Summon $summon
* @return Summon
* @param Summons $summons
* @return Summons
* @throws Exception
*/
public function respond(Summon $summon)
public function respond(Summons $summons)
{
if (!$this->isSummoned($summon)) {
if (!$this->isSummoned($summons)) {
throw new Exception('User is not summoned');
}
$summon
$summons
->setTtl(10 * 60);
$this->repository->add($summon);
$this->repository->add($summons);
return $summon;
return $summons;
}
/**
......
......@@ -33,7 +33,7 @@ class Repository
/**
* @param array $opts
* @return Generator
* @yields array
* @yields Summons[]
* @throws Exception
*/
public function getList(array $opts = [])
......@@ -82,15 +82,15 @@ class Repository
$rows = $this->db->request($prepared);
foreach ($rows as $row) {
$summon = new Summon();
$summon
$summons = new Summons();
$summons
->setReportUrn($row['report_urn'])
->setJuryType($row['jury_type'])
->setJurorGuid($row['juror_guid']->toInt())
->setStatus($row['status'])
->setTtl($row['expires'] - time());
yield $summon;
yield $summons;
}
} catch (Exception $e) {
error_log($e);
......@@ -99,21 +99,21 @@ class Repository
}
/**
* @param Summon $summon
* @param Summons $summons
* @return bool
*/
public function add(Summon $summon)
public function add(Summons $summons)
{
$expires = time() + ((int) $summon->getTtl());
$expires = time() + ((int) $summons->getTtl());
$cql = "INSERT INTO moderation_summons (report_urn, jury_type, juror_guid, status, expires) VALUES (?, ?, ?, ?, ?) USING TTL ?";
$values = [
$summon->getReportUrn(),
$summon->getJuryType(),
new Bigint($summon->getJurorGuid()),
$summon->getStatus(),
$summons->getReportUrn(),
$summons->getJuryType(),
new Bigint($summons->getJurorGuid()),
$summons->getStatus(),
$expires,
(int) $summon->getTtl(),
(int) $summons->getTtl(),
];
$prepared = new Custom();
......@@ -128,16 +128,16 @@ class Repository
}
/**
* @param Summon $summon
* @param Summons $summons
* @return bool
*/
public function exists(Summon $summon)
public function exists(Summons $summons)
{
$cql = "SELECT COUNT(*) as total FROM moderation_summons WHERE report_urn = ? AND jury_type = ? AND juror_guid = ?";
$values = [
$summon->getReportUrn(),
$summon->getJuryType(),
new Bigint($summon->getJurorGuid()),
$summons->getReportUrn(),
$summons->getJuryType(),
new Bigint($summons->getJurorGuid()),
];
$prepared = new Custom();
......@@ -154,16 +154,16 @@ class Repository
}
/**
* @param Summon $summon
* @param Summons $summons
* @return bool
* @throws Exception
*/
public function delete(Summon $summon)
public function delete(Summons $summons)
{
return $this->deleteAll([
'report_urn' => $summon->getReportUrn(),
'jury_type' => $summon->getJuryType(),
'juror_guid' => $summon->getJurorGuid(),
'report_urn' => $summons->getReportUrn(),
'jury_type' => $summons->getJuryType(),
'juror_guid' => $summons->getJurorGuid(),
]);
}
......
......@@ -15,16 +15,16 @@ use Minds\Traits\MagicAttributes;
* Class Summon
* @package Minds\Core\Reports\Summons
* @method string getReportUrn()
* @method Summon setReportUrn(string $reportUrn)
* @method Summons setReportUrn(string $reportUrn)
* @method string getJuryType()
* @method Summon setJuryType(string $juryType)
* @method Summons setJuryType(string $juryType)
* @method int|string getJurorGuid()
* @method Summon setJurorGuid(int|string $jurorGuid)
* @method Summons setJurorGuid(int|string $jurorGuid)
* @method string getStatus()
* @method int getTtl()
* @method Summon setTtl(int $ttl)
* @method Summons setTtl(int $ttl)
*/
class Summon implements JsonSerializable
class Summons implements JsonSerializable
{
use MagicAttributes;
......
<?php
/**
* MetricsDelegate
*
* @author edgebal
*/
namespace Minds\Core\Reports\Verdict\Delegates;
use Exception;
use Minds\Core\Analytics\Metrics\Event;
use Minds\Core\Reports\Jury\Decision;
use Minds\Core\Reports\Verdict\Verdict;
use Minds\Entities\User;
class MetricsDelegate
{
/**
* @param Verdict $verdict
* @throws Exception
*/
public function onCast(Verdict $verdict)
{
$decisions = $verdict->isAppeal() ?
$verdict->getReport()->getAppealJuryDecisions() :
$verdict->getReport()->getInitialJuryDecisions();
$jurorGuids = array_map(function(Decision $decision) {
return $decision->getJurorGuid();
}, $decisions);
foreach ($jurorGuids as $jurorGuid) {
$juror = new User($jurorGuid);
$event = new Event();
$event
->setType('action')
->setAction('jury_duty')
->setProduct('platform')
->setUserGuid($juror->guid)
->setUserPhoneNumberHash($juror->getPhoneNumberHash())
->setEntityGuid($verdict->getReport()->getUrn())
->push();
}
}
}
......@@ -7,6 +7,7 @@
namespace Minds\Core\Reports\Verdict\Delegates;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Reports\Summons\Manager;
use Minds\Core\Reports\Verdict\Verdict;
......@@ -29,7 +30,7 @@ class ReleaseSummonsesDelegate
/**
* @param Verdict $verdict
* @throws \Exception
* @throws Exception
*/
public function onCast(Verdict $verdict)
{
......
......@@ -35,12 +35,16 @@ class Manager
/** @var Delegates\ReleaseSummonsesDelegate $releaseSummonsesDelegate */
private $releaseSummonsesDelegate;
/** @var Delegates\MetricsDelegate $metricsDelegate */
private $metricsDelegate;
public function __construct(
$repository = null,
$actionDelegate = null,
$reverseActionDelegate = null,
$notificationDelegate = null,
$releaseSummonsesDelegate = null
$releaseSummonsesDelegate = null,
$metricsDelegate = null
)
{
$this->repository = $repository ?: new Repository;
......@@ -48,6 +52,7 @@ class Manager
$this->reverseActionDelegate = $reverseActionDelegate ?: new Delegates\ReverseActionDelegate;
$this->notificationDelegate = $notificationDelegate ?: new Delegates\NotificationDelegate;
$this->releaseSummonsesDelegate = $releaseSummonsesDelegate ?: new Delegates\ReleaseSummonsesDelegate;
$this->metricsDelegate = $metricsDelegate ?: new Delegates\MetricsDelegate;
}
/**
......@@ -136,6 +141,9 @@ class Manager
// Reverse the action (if appeal)
$this->reverseActionDelegate->onReverse($verdict);
// Save metrics (for contributions)
$this->metricsDelegate->onCast($verdict);
// Send a notification to the reported user
$this->notificationDelegate->onAction($verdict);
......
......@@ -11,6 +11,7 @@ class ContributionValues
'subscribers' => 4,
'referrals' => 10,
'checkin' => 2,
'jury_duty' => 25,
];
}
......@@ -113,6 +113,16 @@ class UsersIterator implements \Iterator
$field = 'user_guid.keyword';
}
if ($this->action == 'jury_duty') {
$bool['filter'] = [
'term' => [
'action' => 'jury_duty'
]
];
$field = 'user_guid.keyword';
}
$query = [
'index' => 'minds-metrics-*',
'type' => 'action',
......
<?php
namespace Spec\Minds\Core\Reports\Appeals\Delegates;
use Minds\Core\Queue\Interfaces\QueueClient;
use Minds\Core\Queue\Runners\ReportsAppealSummon;
use Minds\Core\Reports\Appeals\Appeal;
use Minds\Core\Reports\Appeals\Delegates\SummonDelegate;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class SummonDelegateSpec extends ObjectBehavior
{
/** @var QueueClient */
protected $queue;
function let(
QueueClient $queue
)
{
$this->beConstructedWith($queue);
$this->queue = $queue;
}
function it_is_initializable()
{
$this->shouldHaveType(SummonDelegate::class);
}
function it_should_queue_on_appeal(Appeal $appeal)
{
$this->queue->setQueue(ReportsAppealSummon::class)
->shouldBeCalled()
->willReturn($this->queue);
$this->queue->send([
'appeal' => $appeal,
'cohort' => null,
])
->shouldBeCalled();
$this
->shouldNotThrow()
->duringOnAppeal($appeal);
}
}
<?php
namespace Spec\Minds\Core\Reports\Summons;
use Minds\Core\Reports\Summons\Cohort;
use Minds\Core\Reports\Summons\Pool;
use Minds\Core\Reports\Summons\Repository;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class CohortSpec extends ObjectBehavior
{
/** @var Repository */
protected $repository;
/** @var Pool */
protected $pool;
function let(
Repository $repository,
Pool $pool
)
{
$this->beConstructedWith($repository, $pool, 20, 3);
$this->repository = $repository;
$this->pool = $pool;
}
function it_is_initializable()
{
$this->shouldHaveType(Cohort::class);
}
function it_should_pick()
{
// 1st iteration
$this->pool->getList([
'active_threshold' => 100,
'platform' => 'browser',
'for' => 1000,
'except' => [ 1001, 1002 ],
'validated' => true,
'size' => 20,
'page' => 0,
'max_pages' => 3,
])
->shouldBeCalled()
->willReturn([ 1010 ]);
// 2nd iteration
$this->pool->getList([
'active_threshold' => 100,
'platform' => 'browser',
'for' => 1000,
'except' => [ 1001, 1002 ],
'validated' => true,
'size' => 20,
'page' => 1,
'max_pages' => 3,
])
->shouldBeCalled()
->willReturn([ 1011 ]);
// 3rd iteration
$this->pool->getList([
'active_threshold' => 100,
'platform' => 'browser',
'for' => 1000,
'except' => [ 1001, 1002 ],
'validated' => true,
'size' => 20,
'page' => 2,
'max_pages' => 3,
])
->shouldBeCalled()
->willReturn([ 1012, 1013, 1014 ]);
$this
->pick([
'size' => 3,
'for' => 1000,
'except' => [ 1001, 1002 ],
'active_threshold' => 100,
])
->shouldReturn([ 1010, 1011, 1012 ]);
}
}
<?php
namespace Spec\Minds\Core\Reports\Summons\Delegates;
use Minds\Core\Reports\Summons\Delegates\SocketDelegate;
use Minds\Core\Reports\Summons\Summons;
use Minds\Core\Sockets\Events as SocketEvents;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class SocketDelegateSpec extends ObjectBehavior
{
/** @var SocketEvents */
protected $socketEvents;
function let(
SocketEvents $socketEvents
)
{
$this->beConstructedWith($socketEvents);
$this->socketEvents = $socketEvents;
}
function it_is_initializable()
{
$this->shouldHaveType(SocketDelegate::class);
}
function it_should_emit_on_summon(Summons $summons)
{
$summons->getJurorGuid()
->shouldBeCalled()
->willReturn(1000);
$summons->jsonSerialize()
->shouldBeCalled()
->willReturn([ 'mock' => 'phpspec' ]);
$this->socketEvents->setUser(1000)
->shouldBeCalled()
->willReturn($this->socketEvents);
$this->socketEvents->emit('moderation_summon', json_encode([ 'mock' => 'phpspec' ]))
->shouldBeCalled();
$this
->onSummon($summons);
}
}
<?php
namespace Spec\Minds\Core\Reports\Summons;
use Minds\Core\Reports\Summons\Manager;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
}
<?php
namespace Spec\Minds\Core\Reports\Summons;
use Minds\Core\Reports\Summons\Pool;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class PoolSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Pool::class);
}
}
<?php
namespace Spec\Minds\Core\Reports\Summons;
use Minds\Core\Reports\Summons\Repository;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RepositorySpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Repository::class);
}
}
<?php
namespace Spec\Minds\Core\Reports\Summons;
use Exception;
use Minds\Core\Reports\Summons\Summons;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class SummonsSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Summons::class);
}
function it_should_set_valid_status()
{
$this
->shouldNotThrow()
->duringSetStatus('awaiting');
$this
->shouldNotThrow()
->duringSetStatus('accepted');
$this
->shouldNotThrow()
->duringSetStatus('declined');
$this
->shouldThrow(new Exception('Invalid status'))
->duringSetStatus(null);
$this
->shouldThrow(new Exception('Invalid status'))
->duringSetStatus('phpspec:invalidstatus');
}
function it_should_return_bool_if_awaiting()
{
$this
->isAwaiting()
->shouldReturn(false);
$this->setStatus('awaiting');
$this
->isAwaiting()
->shouldReturn(true);
}
function it_should_return_bool_if_accepted()
{
$this
->isAccepted()
->shouldReturn(false);
$this->setStatus('accepted');
$this
->isAccepted()
->shouldReturn(true);
}
function it_should_return_bool_if_declined()
{
$this
->isDeclined()
->shouldReturn(false);
$this->setStatus('declined');
$this
->isDeclined()
->shouldReturn(true);
}
}
<?php
namespace Spec\Minds\Core\Reports\Verdict\Delegates;
use Minds\Core\Reports\Verdict\Delegates\MetricsDelegate;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class MetricsDelegateSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(MetricsDelegate::class);
}
}
<?php
namespace Spec\Minds\Core\Reports\Verdict\Delegates;
use Exception;
use Minds\Core\Reports\Report;
use Minds\Core\Reports\Summons\Manager;
use Minds\Core\Reports\Verdict\Delegates\ReleaseSummonsesDelegate;
use Minds\Core\Reports\Verdict\Verdict;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ReleaseSummonsesDelegateSpec extends ObjectBehavior
{
/** @var Manager */
protected $summonsManager;
function let(
Manager $summonsManager
)
{
$this->beConstructedWith($summonsManager);
$this->summonsManager = $summonsManager;
}
function it_is_initializable()
{
$this->shouldHaveType(ReleaseSummonsesDelegate::class);
}
function it_should_release_initial_jury_on_cast(Verdict $verdict, Report $report)
{
$verdict->isAppeal()
->shouldBeCalled()
->willReturn(false);
$verdict->getReport()
->shouldBeCalled()
->willReturn($report);
$report->getUrn()
->shouldBeCalled()
->willReturn('urn:report:phpspec');
$this->summonsManager->release('urn:report:phpspec', 'initial_jury')
->shouldBeCalled();
$this
->shouldNotThrow()
->duringOnCast($verdict);
}
function it_should_release_appeal_jury_on_cast(Verdict $verdict, Report $report)
{
$verdict->isAppeal()
->shouldBeCalled()
->willReturn(true);
$verdict->getReport()
->shouldBeCalled()
->willReturn($report);
$report->getUrn()
->shouldBeCalled()
->willReturn('urn:report:phpspec');
$this->summonsManager->release('urn:report:phpspec', 'appeal_jury')
->shouldBeCalled();
$this
->shouldNotThrow()
->duringOnCast($verdict);
}
}
......@@ -19,21 +19,31 @@ class ManagerSpec extends ObjectBehavior
private $reverseDelegate;
private $notificationDelegate;
private $releaseSummonsesDelegate;
private $metricsDelegate;
function let(
Repository $repository,
Delegates\ActionDelegate $actionDelegate,
Delegates\ReverseActionDelegate $reverseDelegate,
Delegates\NotificationDelegate $notificationDelegate,
Delegates\ReleaseSummonsesDelegate $releaseSummonsesDelegate
Delegates\ReleaseSummonsesDelegate $releaseSummonsesDelegate,
Delegates\MetricsDelegate $metricsDelegate
)
{
$this->beConstructedWith($repository, $actionDelegate, $reverseDelegate, $notificationDelegate, $releaseSummonsesDelegate);
$this->beConstructedWith(
$repository,
$actionDelegate,
$reverseDelegate,
$notificationDelegate,
$releaseSummonsesDelegate,
$metricsDelegate);
$this->repository = $repository;
$this->actionDelegate = $actionDelegate;
$this->reverseDelegate = $reverseDelegate;
$this->notificationDelegate = $notificationDelegate;
$this->releaseSummonsesDelegate = $releaseSummonsesDelegate;
$this->metricsDelegate = $metricsDelegate;
}
......@@ -60,6 +70,9 @@ class ManagerSpec extends ObjectBehavior
$this->actionDelegate->onAction($verdict)
->shouldBeCalled();
$this->metricsDelegate->onCast($verdict)
->shouldBeCalled();
$this->notificationDelegate->onAction($verdict)
->shouldBeCalled();
......