From c1a51d8be4e65360e661bfb98afe69f07017dba1 Mon Sep 17 00:00:00 2001 From: Pavel Dohnal Date: Wed, 14 Apr 2021 09:57:48 +0200 Subject: [PATCH] Add filter class [MAILPOET-3224] --- lib/DI/ContainerConfigurator.php | 1 + .../Filters/EmailOpensAbsoluteCountAction.php | 46 +++++ .../EmailOpensAbsoluteCountActionTest.php | 188 ++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 lib/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountAction.php create mode 100644 tests/integration/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountActionTest.php diff --git a/lib/DI/ContainerConfigurator.php b/lib/DI/ContainerConfigurator.php index d96e556d45..21f340c27e 100644 --- a/lib/DI/ContainerConfigurator.php +++ b/lib/DI/ContainerConfigurator.php @@ -269,6 +269,7 @@ class ContainerConfigurator implements IContainerConfigurator { $container->autowire(\MailPoet\Segments\DynamicSegments\DynamicSegmentsListingRepository::class)->setPublic(true); $container->autowire(\MailPoet\Segments\DynamicSegments\FilterHandler::class)->setPublic(true); $container->autowire(\MailPoet\Segments\DynamicSegments\Filters\EmailAction::class)->setPublic(true); + $container->autowire(\MailPoet\Segments\DynamicSegments\Filters\EmailOpensAbsoluteCountAction::class)->setPublic(true); $container->autowire(\MailPoet\Segments\DynamicSegments\Filters\UserRole::class)->setPublic(true); $container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct::class)->setPublic(true); $container->autowire(\MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory::class)->setPublic(true); diff --git a/lib/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountAction.php b/lib/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountAction.php new file mode 100644 index 0000000000..6d1c8b5aad --- /dev/null +++ b/lib/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountAction.php @@ -0,0 +1,46 @@ +entityManager = $entityManager; + } + + public function apply(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter): QueryBuilder { + $filterData = $filter->getFilterData(); + $days = $filterData->getParam('days'); + $operator = $filterData->getParam('operator'); + $statsTable = $this->entityManager->getClassMetadata(StatisticsOpenEntity::class)->getTableName(); + $subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName(); + $queryBuilder->addSelect("count(opens.id) as oc"); + $queryBuilder->innerJoin( + $subscribersTable, + $statsTable, + 'opens', + "$subscribersTable.id = opens.subscriber_id AND opens.created_at > :newer" . $filter->getId() + ); + $queryBuilder->setParameter('newer' . $filter->getId(), CarbonImmutable::now()->subDays($days)->startOfDay()); + $queryBuilder->groupBy("$subscribersTable.id"); + if ($operator === 'less') { + $queryBuilder->having("oc < :opens" . $filter->getId()); + } else { + $queryBuilder->having("oc > :opens" . $filter->getId()); + } + $queryBuilder->setParameter('opens' . $filter->getId(), $filterData->getParam('opens')); + return $queryBuilder; + } + +} diff --git a/tests/integration/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountActionTest.php b/tests/integration/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountActionTest.php new file mode 100644 index 0000000000..18d4988460 --- /dev/null +++ b/tests/integration/Segments/DynamicSegments/Filters/EmailOpensAbsoluteCountActionTest.php @@ -0,0 +1,188 @@ +cleanData(); + $this->action = $this->diContainer->get(EmailOpensAbsoluteCountAction::class); + $newsletter1 = $this->createNewsletter(); + $newsletter2 = $this->createNewsletter(); + $newsletter3 = $this->createNewsletter(); + $this->entityManager->flush(); + + $subscriber = $this->createSubscriber('opened-3-newsletters@example.com'); + + $this->createStatsNewsletter($subscriber, $newsletter1); + $open = $this->createStatisticsOpens($subscriber, $newsletter1); + $open->setCreatedAt(CarbonImmutable::now()->subMinutes(5)); + $this->createStatsNewsletter($subscriber, $newsletter2); + $open = $this->createStatisticsOpens($subscriber, $newsletter2); + $open->setCreatedAt(CarbonImmutable::now()->subDays(1)); + $this->createStatsNewsletter($subscriber, $newsletter3); + $open = $this->createStatisticsOpens($subscriber, $newsletter3); + $open->setCreatedAt(CarbonImmutable::now()->subDays(2)); + + $subscriber = $this->createSubscriber('opened-old-opens@example.com'); + + $this->createStatsNewsletter($subscriber, $newsletter1); + $open = $this->createStatisticsOpens($subscriber, $newsletter1); + $open->setCreatedAt(CarbonImmutable::now()->subDays(3)); + $this->createStatsNewsletter($subscriber, $newsletter2); + $open = $this->createStatisticsOpens($subscriber, $newsletter2); + $open->setCreatedAt(CarbonImmutable::now()->subDays(4)); + $this->createStatsNewsletter($subscriber, $newsletter3); + $open = $this->createStatisticsOpens($subscriber, $newsletter3); + $open->setCreatedAt(CarbonImmutable::now()->subDays(5)); + + $subscriber = $this->createSubscriber('opened-less-opens@example.com'); + + $this->createStatsNewsletter($subscriber, $newsletter1); + $open = $this->createStatisticsOpens($subscriber, $newsletter1); + $open->setCreatedAt(CarbonImmutable::now()->subMinutes(5)); + $this->createStatsNewsletter($subscriber, $newsletter2); + $open = $this->createStatisticsOpens($subscriber, $newsletter2); + $open->setCreatedAt(CarbonImmutable::now()->subDays(1)); + } + + public function testGetOpened() { + $segmentFilter = $this->getSegmentFilter(2, 'more', 3); + $statement = $this->action->apply($this->getQueryBuilder(), $segmentFilter)->execute(); + assert($statement instanceof Statement); + $result = $statement->fetchAll(); + expect(count($result))->equals(1); + $subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']); + assert($subscriber1 instanceof SubscriberEntity); + expect($subscriber1->getEmail())->equals('opened-3-newsletters@example.com'); + } + + public function testGetOpenedOld() { + $segmentFilter = $this->getSegmentFilter(2, 'more', 7); + $statement = $this->action->apply($this->getQueryBuilder(), $segmentFilter) + ->orderBy('email') + ->execute(); + assert($statement instanceof Statement); + $result = $statement->fetchAll(); + expect(count($result))->equals(2); + $subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']); + assert($subscriber1 instanceof SubscriberEntity); + expect($subscriber1->getEmail())->equals('opened-3-newsletters@example.com'); + $subscriber2 = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']); + assert($subscriber2 instanceof SubscriberEntity); + expect($subscriber2->getEmail())->equals('opened-old-opens@example.com'); + } + + public function testGetOpenedLess() { + $segmentFilter = $this->getSegmentFilter(3, 'less', 3); + $statement = $this->action->apply($this->getQueryBuilder(), $segmentFilter) + ->orderBy('email') + ->execute(); + assert($statement instanceof Statement); + $result = $statement->fetchAll(); + expect(count($result))->equals(2); + $subscriber1 = $this->entityManager->find(SubscriberEntity::class, $result[0]['id']); + assert($subscriber1 instanceof SubscriberEntity); + expect($subscriber1->getEmail())->equals('opened-less-opens@example.com'); + $subscriber2 = $this->entityManager->find(SubscriberEntity::class, $result[1]['id']); + assert($subscriber2 instanceof SubscriberEntity); + expect($subscriber2->getEmail())->equals('opened-old-opens@example.com'); + } + + private function getQueryBuilder() { + $subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName(); + return $this->entityManager + ->getConnection() + ->createQueryBuilder() + ->select("$subscribersTable.id") + ->from($subscribersTable); + } + + private function getSegmentFilter(int $opens, string $operator, int $days): DynamicSegmentFilterEntity { + $segmentFilterData = new DynamicSegmentFilterData([ + 'segmentType' => DynamicSegmentFilterData::TYPE_EMAIL, + 'action' => EmailOpensAbsoluteCountAction::TYPE, + 'operator' => $operator, + 'opens' => $opens, + 'days' => $days, + ]); + $segment = new SegmentEntity('Dynamic Segment', SegmentEntity::TYPE_DYNAMIC, 'description'); + $this->entityManager->persist($segment); + $dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $segmentFilterData); + $this->entityManager->persist($dynamicSegmentFilter); + $segment->addDynamicFilter($dynamicSegmentFilter); + return $dynamicSegmentFilter; + } + + private function createSubscriber(string $email) { + $subscriber = new SubscriberEntity(); + $subscriber->setEmail($email); + $subscriber->setLastName('Last'); + $subscriber->setFirstName('First'); + $subscriber->setStatus(SubscriberEntity::STATUS_SUBSCRIBED); + $this->entityManager->persist($subscriber); + $this->entityManager->flush(); + return $subscriber; + } + + private function createNewsletter() { + $newsletter = new NewsletterEntity(); + $newsletter->setSubject('newsletter 1'); + $newsletter->setStatus('sent'); + $newsletter->setType(NewsletterEntity::TYPE_STANDARD); + $this->entityManager->persist($newsletter); + $task = new ScheduledTaskEntity(); + $this->entityManager->persist($task); + $queue = new SendingQueueEntity(); + $queue->setNewsletter($newsletter); + $queue->setTask($task); + $this->entityManager->persist($queue); + $newsletter->getQueues()->add($queue); + return $newsletter; + } + + private function createStatsNewsletter(SubscriberEntity $subscriber, NewsletterEntity $newsletter) { + $queue = $newsletter->getLatestQueue(); + assert($queue instanceof SendingQueueEntity); + $stats = new StatisticsNewsletterEntity($newsletter, $queue, $subscriber); + $this->entityManager->persist($stats); + $this->entityManager->flush(); + return $stats; + } + + private function createStatisticsOpens(SubscriberEntity $subscriber, NewsletterEntity $newsletter) { + $queue = $newsletter->getLatestQueue(); + assert($queue instanceof SendingQueueEntity); + $open = new StatisticsOpenEntity($newsletter, $queue, $subscriber); + $this->entityManager->persist($open); + $this->entityManager->flush(); + return $open; + } + + public function _after() { + $this->cleanData(); + } + + private function cleanData() { + $this->truncateEntity(NewsletterEntity::class); + $this->truncateEntity(ScheduledTaskEntity::class); + $this->truncateEntity(SendingQueueEntity::class); + $this->truncateEntity(SubscriberEntity::class); + $this->truncateEntity(StatisticsOpenEntity::class); + $this->truncateEntity(StatisticsNewsletterEntity::class); + } +}