Add calculation segment score

[MAILPOET-3533]
This commit is contained in:
Jan Lysý
2021-05-05 08:50:00 +02:00
committed by Veljko V
parent a92943ff30
commit 6d0486cfc5
5 changed files with 105 additions and 7 deletions

View File

@ -103,6 +103,7 @@ class Settings extends APIEndpoint {
public function recalculateSubscribersScore() { public function recalculateSubscribersScore() {
$this->statisticsOpensRepository->resetSubscribersScoreCalculation(); $this->statisticsOpensRepository->resetSubscribersScoreCalculation();
$this->statisticsOpensRepository->resetNewslettersScoreCalculation();
$task = new ScheduledTaskEntity(); $task = new ScheduledTaskEntity();
$task->setType(SubscribersEngagementScore::TASK_TYPE); $task->setType(SubscribersEngagementScore::TASK_TYPE);
$task->setScheduledAt(Carbon::createFromTimestamp($this->wp->currentTime('timestamp'))); $task->setScheduledAt(Carbon::createFromTimestamp($this->wp->currentTime('timestamp')));

View File

@ -3,6 +3,7 @@
namespace MailPoet\Cron\Workers; namespace MailPoet\Cron\Workers;
use MailPoet\Models\ScheduledTask; use MailPoet\Models\ScheduledTask;
use MailPoet\Segments\SegmentsRepository;
use MailPoet\Statistics\StatisticsOpensRepository; use MailPoet\Statistics\StatisticsOpensRepository;
use MailPoet\Subscribers\SubscribersRepository; use MailPoet\Subscribers\SubscribersRepository;
use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Carbon\Carbon;
@ -12,6 +13,9 @@ class SubscribersEngagementScore extends SimpleWorker {
const BATCH_SIZE = 60; const BATCH_SIZE = 60;
const TASK_TYPE = 'subscribers_engagement_score'; const TASK_TYPE = 'subscribers_engagement_score';
/** @var SegmentsRepository */
private $segmentsRepository;
/** @var StatisticsOpensRepository */ /** @var StatisticsOpensRepository */
private $statisticsOpensRepository; private $statisticsOpensRepository;
@ -19,25 +23,47 @@ class SubscribersEngagementScore extends SimpleWorker {
private $subscribersRepository; private $subscribersRepository;
public function __construct( public function __construct(
SegmentsRepository $segmentsRepository,
StatisticsOpensRepository $statisticsOpensRepository, StatisticsOpensRepository $statisticsOpensRepository,
SubscribersRepository $subscribersRepository SubscribersRepository $subscribersRepository
) { ) {
parent::__construct(); parent::__construct();
$this->segmentsRepository = $segmentsRepository;
$this->statisticsOpensRepository = $statisticsOpensRepository; $this->statisticsOpensRepository = $statisticsOpensRepository;
$this->subscribersRepository = $subscribersRepository; $this->subscribersRepository = $subscribersRepository;
} }
public function processTaskStrategy(ScheduledTask $task, $timer) { public function processTaskStrategy(ScheduledTask $task, $timer) {
$subscribers = $this->subscribersRepository->findByUpdatedScoreNotInLastMonth(SubscribersEngagementScore::BATCH_SIZE); $recalculatedSubscribersCount = $this->recalculateSubscribers();
if ($recalculatedSubscribersCount > 0) {
$this->scheduleImmediately();
return true;
}
$recalculatedSegmentsCount = $this->recalculateSegments();
if ($recalculatedSegmentsCount > 0) {
$this->scheduleImmediately();
return true;
}
$this->schedule();
return true;
}
private function recalculateSubscribers(): int {
$subscribers = $this->subscribersRepository->findByUpdatedScoreNotInLastMonth(self::BATCH_SIZE);
foreach ($subscribers as $subscriber) { foreach ($subscribers as $subscriber) {
$this->statisticsOpensRepository->recalculateSubscriberScore($subscriber); $this->statisticsOpensRepository->recalculateSubscriberScore($subscriber);
} }
if ($subscribers) { return count($subscribers);
$this->scheduleImmediately(); }
} else {
$this->schedule(); private function recalculateSegments(): int {
$segments = $this->segmentsRepository->findByUpdatedScoreNotInLastMonth(self::BATCH_SIZE);
foreach ($segments as $segment) {
$this->statisticsOpensRepository->recalculateSegmentScore($segment);
} }
return true; return count($segments);
} }
public function getNextRunDate() { public function getNextRunDate() {

View File

@ -184,4 +184,17 @@ class SegmentsRepository extends Repository {
return $rows; return $rows;
} }
public function findByUpdatedScoreNotInLastMonth(int $limit): array {
$dateTime = (new Carbon())->subMonths(1);
return $this->entityManager->createQueryBuilder()
->select('s')
->from(SegmentEntity::class, 's')
->where('s.averageEngagementScoreUpdatedAt IS NULL')
->orWhere('s.averageEngagementScoreUpdatedAt < :dateTime')
->setParameter('dateTime', $dateTime)
->getQuery()
->setMaxResults($limit)
->getResult();
}
} }

View File

@ -3,6 +3,7 @@
namespace MailPoet\Statistics; namespace MailPoet\Statistics;
use MailPoet\Doctrine\Repository; use MailPoet\Doctrine\Repository;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\StatisticsNewsletterEntity; use MailPoet\Entities\StatisticsNewsletterEntity;
use MailPoet\Entities\StatisticsOpenEntity; use MailPoet\Entities\StatisticsOpenEntity;
use MailPoet\Entities\SubscriberEntity; use MailPoet\Entities\SubscriberEntity;
@ -58,4 +59,54 @@ class StatisticsOpensRepository extends Repository {
->setParameter('verified', null) ->setParameter('verified', null)
->getQuery()->execute(); ->getQuery()->execute();
} }
public function recalculateSegmentScore(SegmentEntity $segment): void {
$segment->setAverageEngagementScoreUpdatedAt(new \DateTimeImmutable());
$dateTime = (new Carbon())->subYear();
$newslettersSentCount = $this
->entityManager
->createQueryBuilder()
->select('count(DISTINCT statisticsNewsletter.newsletter)')
->from(StatisticsNewsletterEntity::class, 'statisticsNewsletter')
->join('statisticsNewsletter.subscriber', 'subscriber')
->join('subscriber.subscriberSegments', 'subscriberSegments')
->where('subscriber.status = :subscribed')
->andWhere('subscriberSegments.segment = :segment')
->andWhere('subscriberSegments.status = :subscribed')
->andWhere('subscriber.deletedAt IS NULL')
->andWhere('subscriber.engagementScore IS NOT NULL')
->andWhere('statisticsNewsletter.sentAt > :dateTime')
->setParameter('segment', $segment)
->setParameter('subscribed', SubscriberEntity::STATUS_SUBSCRIBED)
->setParameter('dateTime', $dateTime)
->getQuery()
->getSingleScalarResult();
if ($newslettersSentCount < 3) {
$this->entityManager->flush();
return;
}
$avgScore = $this
->entityManager
->createQueryBuilder()
->select('avg(DISTINCT subscriber.engagementScore)')
->from(SubscriberEntity::class, 'subscriber')
->join('subscriber.subscriberSegments', 'subscriberSegments')
->where('subscriberSegments.segment = :segment')
->andWhere('subscriber.status = :subscribed')
->andWhere('subscriberSegments.status = :subscribed')
->andWhere('subscriber.engagementScore IS NOT NULL')
->setParameter('segment', $segment)
->setParameter('subscribed', SubscriberEntity::STATUS_SUBSCRIBED)
->getQuery()
->getSingleScalarResult();
$segment->setAverageEngagementScore((float)$avgScore);
$this->entityManager->flush();
}
public function resetNewslettersScoreCalculation(): void {
$this->entityManager->createQueryBuilder()->update(SegmentEntity::class, 's')
->set('s.averageEngagementScoreUpdatedAt', ':verified')
->setParameter('verified', null)
->getQuery()->execute();
}
} }

View File

@ -36,6 +36,13 @@ class Opens {
$queue->getId() $queue->getId()
); );
$this->statisticsOpensRepository->recalculateSubscriberScore($subscriber); $this->statisticsOpensRepository->recalculateSubscriberScore($subscriber);
foreach ($newsletter->getNewsletterSegments() as $newsletterSegment) {
$segment = $newsletterSegment->getSegment();
if (!$segment) {
continue;
}
$this->statisticsOpensRepository->recalculateSegmentScore($segment);
}
} }
return $this->returnResponse($displayImage); return $this->returnResponse($displayImage);
} }