Enable joining segments using and/or

[MAILPOET-3212]
This commit is contained in:
Pavel Dohnal
2021-03-16 12:57:30 +01:00
committed by Veljko V
parent ceae8e17aa
commit 5b45bdac1e
6 changed files with 120 additions and 6 deletions

View File

@ -11,6 +11,7 @@ use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
use MailPoet\Segments\DynamicSegments\Filters\UserRole;
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceCategory;
use MailPoet\Segments\DynamicSegments\Filters\WooCommerceProduct;
use MailPoet\Util\Security;
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
use MailPoetVendor\Doctrine\ORM\EntityManager;
@ -61,7 +62,28 @@ class FilterHandler {
$queryBuilder->getParameters()
));
}
$queryBuilder->innerJoin($subscribersTable, sprintf('(%s)', join(' UNION ', $filterSelects)), 'filtered_subscribers', 'filtered_subscribers.inner_subscriber_id = id');
$this->joinSubqueries($queryBuilder, $segment, $filterSelects);
return $queryBuilder;
}
private function joinSubqueries(QueryBuilder $queryBuilder, SegmentEntity $segment, array $subQueries): QueryBuilder {
$filter = $segment->getDynamicFilters()->first();
if (!$filter) return $queryBuilder;
$filterData = $filter->getFilterData();
$data = $filterData->getData();
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
if (!isset($data['connect']) || $data['connect'] === 'or') {
// the final query: SELECT * FROM subscribers INNER JOIN (filter_select1 UNION filter_select2) filtered_subscribers ON filtered_subscribers.inner_subscriber_id = id
$queryBuilder->innerJoin($subscribersTable, sprintf('(%s)', join(' UNION ', $subQueries)), 'filtered_subscribers', 'filtered_subscribers.inner_subscriber_id = id');
return $queryBuilder;
}
foreach ($subQueries as $subQuery) {
// we need a unique name for each subquery so that we can join them together in the sql query - just make sure the identifier starts with a letter, not a number
$subqueryName = 'a' . Security::generateRandomString(5);
$queryBuilder->innerJoin($subscribersTable, "($subQuery)", $subqueryName, "$subqueryName.inner_subscriber_id = id");
}
return $queryBuilder;
}

View File

@ -2,7 +2,6 @@
namespace MailPoet\Segments\DynamicSegments\Filters;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\StatisticsClickEntity;
use MailPoet\Entities\StatisticsNewsletterEntity;

View File

@ -2,7 +2,6 @@
namespace MailPoet\Segments\DynamicSegments\Filters;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;

View File

@ -2,7 +2,6 @@
namespace MailPoet\Segments\DynamicSegments\Filters;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\WP\Functions as WPFunctions;

View File

@ -2,7 +2,6 @@
namespace MailPoet\Segments\DynamicSegments\Filters;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;

View File

@ -4,14 +4,26 @@ namespace MailPoet\Segments\DynamicSegments;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Entities\StatisticsOpenEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Segments\DynamicSegments\Filters\EmailAction;
use MailPoet\Subscribers\SubscribersRepository;
class FilterHandlerTest extends \MailPoetTest {
/** @var FilterHandler */
private $filterHandler;
/** @var SubscriberEntity */
private $subscriber1;
/** @var SubscriberEntity */
private $subscriber2;
public function _before() {
$this->cleanWpUsers();
$this->filterHandler = $this->diContainer->get(FilterHandler::class);
@ -30,6 +42,12 @@ class FilterHandlerTest extends \MailPoetTest {
'email' => 'user-role-test3@example.com',
'wp_user_id' => $id,
]);
// fetch entities
/** @var SubscribersRepository $subscribersRepository */
$subscribersRepository = $this->diContainer->get(SubscribersRepository::class);
$this->subscriber1 = $subscribersRepository->findOneBy(['email' => 'user-role-test1@example.com']);
$this->subscriber2 = $subscribersRepository->findOneBy(['email' => 'user-role-test2@example.com']);
}
public function testItAppliesFilter() {
@ -43,7 +61,7 @@ class FilterHandlerTest extends \MailPoetTest {
expect($subscriber2->getEmail())->equals('user-role-test3@example.com');
}
public function testItAppliesTwoFilters() {
public function testItAppliesTwoFiltersWithoutSpecifyingConnection() {
$segment = $this->getSegment('editor');
$filter = new DynamicSegmentFilterData([
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
@ -52,11 +70,84 @@ class FilterHandlerTest extends \MailPoetTest {
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $filter);
$this->entityManager->persist($dynamicSegmentFilter);
$segment->addDynamicFilter($dynamicSegmentFilter);
$this->entityManager->flush();
$queryBuilder = $this->filterHandler->apply($this->getQueryBuilder(), $segment);
$result = $queryBuilder->execute()->fetchAll();
expect($result)->count(3);
}
public function testItAppliesTwoFiltersWithOr() {
$segment = new SegmentEntity('Dynamic Segment', SegmentEntity::TYPE_DYNAMIC, 'description');
$this->entityManager->persist($segment);
$filterData = new DynamicSegmentFilterData([
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
'wordpressRole' => 'administrator',
'connect' => 'or',
]);
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $filterData);
$this->entityManager->persist($dynamicSegmentFilter);
$segment->addDynamicFilter($dynamicSegmentFilter);
$filterData = new DynamicSegmentFilterData([
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
'wordpressRole' => 'editor',
'connect' => 'or',
]);
$dynamicSegmentFilter = new DynamicSegmentFilterEntity($segment, $filterData);
$this->entityManager->persist($dynamicSegmentFilter);
$segment->addDynamicFilter($dynamicSegmentFilter);
$this->entityManager->flush();
$queryBuilder = $this->filterHandler->apply($this->getQueryBuilder(), $segment);
$result = $queryBuilder->execute()->fetchAll();
expect($result)->count(3);
}
public function testItAppliesTwoFiltersWithAnd() {
$segment = new SegmentEntity('Dynamic Segment', SegmentEntity::TYPE_DYNAMIC, 'description');
$this->entityManager->persist($segment);
// filter user is an editor
$editorData = new DynamicSegmentFilterData([
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
'wordpressRole' => 'editor',
'connect' => 'and',
]);
$filterEditor = new DynamicSegmentFilterEntity($segment, $editorData);
$this->entityManager->persist($filterEditor);
$segment->addDynamicFilter($filterEditor);
// filter user opened an email
$newsletter = new NewsletterEntity();
$task = new ScheduledTaskEntity();
$this->entityManager->persist($task);
$queue = new SendingQueueEntity();
$queue->setNewsletter($newsletter);
$queue->setTask($task);
$this->entityManager->persist($queue);
$newsletter->getQueues()->add($queue);
$newsletter->setSubject('newsletter 1');
$newsletter->setStatus('sent');
$newsletter->setType(NewsletterEntity::TYPE_STANDARD);
$this->entityManager->persist($newsletter);
$open = new StatisticsOpenEntity($newsletter, $queue, $this->subscriber1);
$this->entityManager->persist($open);
$open = new StatisticsOpenEntity($newsletter, $queue, $this->subscriber2);
$this->entityManager->persist($open);
$this->entityManager->flush();
$openedData = new DynamicSegmentFilterData([
'segmentType' => DynamicSegmentFilterData::TYPE_EMAIL,
'action' => EmailAction::ACTION_OPENED,
'newsletter_id' => $newsletter->getId(),
'connect' => 'and',
]);
$filterOpened = new DynamicSegmentFilterEntity($segment, $openedData);
$this->entityManager->persist($filterOpened);
$segment->addDynamicFilter($filterOpened);
$this->entityManager->flush();
$queryBuilder = $this->filterHandler->apply($this->getQueryBuilder(), $segment);
$result = $queryBuilder->execute()->fetchAll();
expect($result)->count(1);
}
private function getSegment(string $role): SegmentEntity {
$filter = new DynamicSegmentFilterData([
'segmentType' => DynamicSegmentFilterData::TYPE_USER_ROLE,
@ -84,6 +175,11 @@ class FilterHandlerTest extends \MailPoetTest {
$this->cleanWpUsers();
$this->truncateEntity(SubscriberEntity::class);
$this->truncateEntity(SegmentEntity::class);
$this->truncateEntity(DynamicSegmentFilterEntity::class);
$this->truncateEntity(NewsletterEntity::class);
$this->truncateEntity(StatisticsOpenEntity::class);
$this->truncateEntity(SendingQueueEntity::class);
$this->truncateEntity(ScheduledTaskEntity::class);
}
private function cleanWpUsers() {