Add "was sent email" filter

MAILPOET-5004
This commit is contained in:
John Oleksowicz
2023-04-10 14:47:49 -05:00
committed by Aschepikov
parent 87f703f22a
commit 8a66c83257
6 changed files with 116 additions and 3 deletions

View File

@ -43,6 +43,7 @@ const componentsMap = {
EmailOpensAbsoluteCountFields,
[EmailActionTypes.CLICKED]: EmailClickStatisticsFields,
[EmailActionTypes.OPENED]: EmailOpenStatisticsFields,
[EmailActionTypes.WAS_SENT]: EmailOpenStatisticsFields,
[EmailActionTypes.MACHINE_OPENED]: EmailOpenStatisticsFields,
[EmailActionTypes.CLICKED_ANY]: null,
};

View File

@ -12,6 +12,11 @@ export const EmailSegmentOptions = [
label: MailPoet.I18n.t('emailActionMachineOpensAbsoluteCount'),
group: SegmentTypes.Email,
},
{
value: EmailActionTypes.WAS_SENT,
label: MailPoet.I18n.t('emailActionWasSent'),
group: SegmentTypes.Email,
},
{
value: EmailActionTypes.OPENED,
label: MailPoet.I18n.t('emailActionOpened'),

View File

@ -12,6 +12,7 @@ export enum EmailActionTypes {
MACHINE_OPENS_ABSOLUTE_COUNT = 'machineOpensAbsoluteCount',
OPENED = 'opened',
MACHINE_OPENED = 'machineOpened',
WAS_SENT = 'wasSent',
CLICKED = 'clicked',
CLICKED_ANY = 'clickedAny',
}

View File

@ -18,16 +18,18 @@ use MailPoetVendor\Doctrine\ORM\EntityManager;
class EmailAction implements Filter {
const ACTION_OPENED = 'opened';
const ACTION_MACHINE_OPENED = 'machineOpened';
/** @deprecated */
/** @deprecated */
const ACTION_NOT_OPENED = 'notOpened';
const ACTION_CLICKED = 'clicked';
/** @deprecated */
const ACTION_WAS_SENT = 'wasSent';
/** @deprecated */
const ACTION_NOT_CLICKED = 'notClicked';
const ALLOWED_ACTIONS = [
self::ACTION_OPENED,
self::ACTION_MACHINE_OPENED,
self::ACTION_CLICKED,
self::ACTION_WAS_SENT,
EmailActionClickAny::TYPE,
EmailOpensAbsoluteCountAction::TYPE,
EmailOpensAbsoluteCountAction::MACHINE_TYPE,
@ -35,11 +37,15 @@ class EmailAction implements Filter {
/** @var EntityManager */
private $entityManager;
/** @var FilterHelper */
private $filterHelper;
public function __construct(
EntityManager $entityManager
EntityManager $entityManager,
FilterHelper $filterHelper
) {
$this->entityManager = $entityManager;
$this->filterHelper = $filterHelper;
}
public function apply(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter): QueryBuilder {
@ -49,6 +55,8 @@ class EmailAction implements Filter {
if ($action === self::ACTION_CLICKED) {
return $this->applyForClickedActions($queryBuilder, $filterData, $parameterSuffix);
} elseif ($action === self::ACTION_WAS_SENT) {
return $this->applyForWasSentAction($queryBuilder, $filterData, $parameterSuffix);
} else {
return $this->applyForOpenedActions($queryBuilder, $filterData, $parameterSuffix);
}
@ -175,4 +183,36 @@ class EmailAction implements Filter {
}
return $clause;
}
private function applyForWasSentAction(QueryBuilder $queryBuilder, DynamicSegmentFilterData $filterData, string $parameterSuffix): QueryBuilder {
$newsletters = (array)$filterData->getParam('newsletters');
$operator = $filterData->getParam('operator') ?? DynamicSegmentFilterData::OPERATOR_ANY;
$subscribersTable = $this->filterHelper->getSubscribersTable();
$statisticsNewslettersTable = $this->entityManager->getClassMetadata(StatisticsNewsletterEntity::class)->getTableName();
if ($operator === DynamicSegmentFilterData::OPERATOR_NONE) {
$queryBuilder->leftJoin(
$this->filterHelper->getSubscribersTable(),
$statisticsNewslettersTable,
'statisticsNewsletter',
"$subscribersTable.id = statisticsNewsletter.subscriber_id AND statisticsNewsletter.newsletter_id IN (:newsletters" . $parameterSuffix . ')'
)
->setParameter('newsletters' . $parameterSuffix, $newsletters, Connection::PARAM_INT_ARRAY)
->andWhere('statisticsNewsletter.subscriber_id IS NULL');
} else {
$queryBuilder->innerJoin(
$subscribersTable,
$statisticsNewslettersTable,
'statisticsNewsletter',
"statisticsNewsletter.subscriber_id = $subscribersTable.id AND statisticsNewsletter.newsletter_id IN (:newsletters" . $parameterSuffix . ')'
)->setParameter('newsletters' . $parameterSuffix, $newsletters, Connection::PARAM_INT_ARRAY);
if ($operator === DynamicSegmentFilterData::OPERATOR_ALL) {
$queryBuilder->groupBy('subscriber_id');
$queryBuilder->having('COUNT(1) = ' . count($newsletters));
}
}
return $queryBuilder;
}
}

View File

@ -292,6 +292,71 @@ class EmailActionTest extends \MailPoetTest {
$this->assertEqualsCanonicalizing(['opened_machine@example.com'], $emails);
}
public function testSentEmailAny(): void {
$subscriber1 = $this->createSubscriber('1@example.com');
$subscriber2 = $this->createSubscriber('2@example.com');
$this->createSubscriber('3@example.com');
$this->createStatsNewsletter($subscriber1, $this->newsletter);
$this->createStatsNewsletter($subscriber2, $this->newsletter2);
$segmentFilterData = $this->getSegmentFilterData(EmailAction::ACTION_WAS_SENT, [
'newsletters' => [$this->newsletter->getId(), $this->newsletter2->getId()],
'operator' => DynamicSegmentFilterData::OPERATOR_ANY,
]);
$emails = $this->tester->getSubscriberEmailsMatchingDynamicFilter($segmentFilterData, $this->emailAction);
expect($emails)->contains('1@example.com');
expect($emails)->contains('2@example.com');
expect($emails)->notContains('3@example.com');
}
public function testSentEmailAll(): void {
$subscriber1 = $this->createSubscriber('1@example.com');
$subscriber2 = $this->createSubscriber('2@example.com');
$subscriber3 = $this->createSubscriber('3@example.com');
$this->createStatsNewsletter($subscriber1, $this->newsletter);
$this->createStatsNewsletter($subscriber1, $this->newsletter2);
$this->createStatsNewsletter($subscriber2, $this->newsletter2);
$this->createStatsNewsletter($subscriber3, $this->newsletter2);
$segmentFilterData = $this->getSegmentFilterData(EmailAction::ACTION_WAS_SENT, [
'newsletters' => [$this->newsletter->getId(), $this->newsletter2->getId()],
'operator' => DynamicSegmentFilterData::OPERATOR_ALL,
]);
$emails = $this->tester->getSubscriberEmailsMatchingDynamicFilter($segmentFilterData, $this->emailAction);
expect($emails)->contains('1@example.com');
expect($emails)->notContains('2@example.com');
expect($emails)->notContains('3@example.com');
}
public function testSentEmailNone(): void {
$subscriber1 = $this->createSubscriber('1@example.com');
$subscriber2 = $this->createSubscriber('2@example.com');
$subscriber3 = $this->createSubscriber('3@example.com');
$this->createStatsNewsletter($subscriber1, $this->newsletter);
$this->createStatsNewsletter($subscriber1, $this->newsletter2);
$this->createStatsNewsletter($subscriber2, $this->newsletter2);
$this->createStatsNewsletter($subscriber3, $this->newsletter3);
$segmentFilterData = $this->getSegmentFilterData(EmailAction::ACTION_WAS_SENT, [
'newsletters' => [$this->newsletter->getId(), $this->newsletter2->getId()],
'operator' => DynamicSegmentFilterData::OPERATOR_NONE,
]);
$emails = $this->tester->getSubscriberEmailsMatchingDynamicFilter($segmentFilterData, $this->emailAction);
expect($emails)->notContains('1@example.com');
expect($emails)->notContains('2@example.com');
expect($emails)->contains('3@example.com');
}
private function getSegmentFilterData(string $action, array $data): DynamicSegmentFilterData {
return new DynamicSegmentFilterData(DynamicSegmentFilterData::TYPE_EMAIL, $action, $data);
}

View File

@ -137,6 +137,7 @@
'selectUserRolePlaceholder': __('Search user roles'),
'selectCustomFieldPlaceholder': __('Select custom field'),
'emailActionOpened': _x('opened', 'Dynamic segment creation: when newsletter was opened'),
'emailActionWasSent': _x('was sent', 'Dynamic segment creation: when newsletter was sent'),
'emailActionMachineOpened': _x('machine-opened', 'Dynamic segment creation: list of all subscribers that opened the newsletter automatically in the background'),
'emailActionOpensAbsoluteCount': __('# of opens'),
'emailActionMachineOpensAbsoluteCount': __('# of machine-opens'),