Display machine opens
[MAILPOET-3740]
This commit is contained in:
@ -22,15 +22,18 @@ export const NewsletterGeneralStats = ({
|
|||||||
|
|
||||||
let percentageClicked = 0;
|
let percentageClicked = 0;
|
||||||
let percentageOpened = 0;
|
let percentageOpened = 0;
|
||||||
|
let percentageMachineOpened = 0;
|
||||||
let percentageUnsubscribed = 0;
|
let percentageUnsubscribed = 0;
|
||||||
if (totalSent > 0) {
|
if (totalSent > 0) {
|
||||||
percentageClicked = (newsletter.statistics.clicked * 100) / totalSent;
|
percentageClicked = (newsletter.statistics.clicked * 100) / totalSent;
|
||||||
percentageOpened = (newsletter.statistics.opened * 100) / totalSent;
|
percentageOpened = (newsletter.statistics.opened * 100) / totalSent;
|
||||||
|
percentageMachineOpened = (newsletter.statistics.machineOpens * 100) / totalSent;
|
||||||
percentageUnsubscribed = (newsletter.statistics.unsubscribed * 100) / totalSent;
|
percentageUnsubscribed = (newsletter.statistics.unsubscribed * 100) / totalSent;
|
||||||
}
|
}
|
||||||
// format to 1 decimal place
|
// format to 1 decimal place
|
||||||
const percentageClickedDisplay = MailPoet.Num.toLocaleFixed(percentageClicked, 1);
|
const percentageClickedDisplay = MailPoet.Num.toLocaleFixed(percentageClicked, 1);
|
||||||
const percentageOpenedDisplay = MailPoet.Num.toLocaleFixed(percentageOpened, 1);
|
const percentageOpenedDisplay = MailPoet.Num.toLocaleFixed(percentageOpened, 1);
|
||||||
|
const percentageMachineOpenedDisplay = MailPoet.Num.toLocaleFixed(percentageMachineOpened, 1);
|
||||||
const percentageUnsubscribedDisplay = MailPoet.Num.toLocaleFixed(percentageUnsubscribed, 1);
|
const percentageUnsubscribedDisplay = MailPoet.Num.toLocaleFixed(percentageUnsubscribed, 1);
|
||||||
|
|
||||||
const displayBadges = ((totalSent >= minNewslettersSent)
|
const displayBadges = ((totalSent >= minNewslettersSent)
|
||||||
@ -47,6 +50,16 @@ export const NewsletterGeneralStats = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const machineOpened = (
|
||||||
|
<div className="mailpoet-statistics-value-small">
|
||||||
|
<span className="mailpoet-statistics-value-number">
|
||||||
|
{percentageMachineOpenedDisplay}
|
||||||
|
{'% '}
|
||||||
|
</span>
|
||||||
|
{MailPoet.I18n.t('percentageMachineOpened')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const unsubscribed = (
|
const unsubscribed = (
|
||||||
<div className="mailpoet-statistics-value-small">
|
<div className="mailpoet-statistics-value-small">
|
||||||
<span className="mailpoet-statistics-value-number">
|
<span className="mailpoet-statistics-value-number">
|
||||||
@ -102,6 +115,7 @@ export const NewsletterGeneralStats = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mailpoet-statistics-with-left-separator">
|
<div className="mailpoet-statistics-with-left-separator">
|
||||||
{opened}
|
{opened}
|
||||||
|
{machineOpened}
|
||||||
</div>
|
</div>
|
||||||
{isWoocommerceActive && (
|
{isWoocommerceActive && (
|
||||||
<div className="mailpoet-statistics-with-left-separator">
|
<div className="mailpoet-statistics-with-left-separator">
|
||||||
|
@ -17,6 +17,7 @@ export type NewsletterType = {
|
|||||||
statistics: {
|
statistics: {
|
||||||
clicked: number;
|
clicked: number;
|
||||||
opened: number;
|
opened: number;
|
||||||
|
machineOpens: number;
|
||||||
unsubscribed: number;
|
unsubscribed: number;
|
||||||
revenue: {
|
revenue: {
|
||||||
value: number;
|
value: number;
|
||||||
|
@ -90,6 +90,7 @@ class NewslettersResponseBuilder {
|
|||||||
->where('tasks.status', SendingQueue::STATUS_SCHEDULED)
|
->where('tasks.status', SendingQueue::STATUS_SCHEDULED)
|
||||||
->count();
|
->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($relation === self::RELATION_STATISTICS) {
|
if ($relation === self::RELATION_STATISTICS) {
|
||||||
$data['statistics'] = $this->newslettersStatsRepository->getStatistics($newsletter)->asArray();
|
$data['statistics'] = $this->newslettersStatsRepository->getStatistics($newsletter)->asArray();
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,9 @@ class NewsletterStatistics {
|
|||||||
/** @var int */
|
/** @var int */
|
||||||
private $openCount;
|
private $openCount;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $machineOpens;
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
private $unsubscribeCount;
|
private $unsubscribeCount;
|
||||||
|
|
||||||
@ -27,49 +30,40 @@ class NewsletterStatistics {
|
|||||||
$this->wooCommerceRevenue = $wooCommerceRevenue;
|
$this->wooCommerceRevenue = $wooCommerceRevenue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getClickCount(): int {
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getClickCount() {
|
|
||||||
return $this->clickCount;
|
return $this->clickCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getOpenCount(): int {
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getOpenCount() {
|
|
||||||
return $this->openCount;
|
return $this->openCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getUnsubscribeCount(): int {
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getUnsubscribeCount() {
|
|
||||||
return $this->unsubscribeCount;
|
return $this->unsubscribeCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getTotalSentCount(): int {
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getTotalSentCount() {
|
|
||||||
return $this->totalSentCount;
|
return $this->totalSentCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getWooCommerceRevenue(): ?WooCommerceRevenue {
|
||||||
* @return WooCommerceRevenue|null
|
|
||||||
*/
|
|
||||||
public function getWooCommerceRevenue() {
|
|
||||||
return $this->wooCommerceRevenue;
|
return $this->wooCommerceRevenue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function setMachineOpens(int $machineOpens): void {
|
||||||
* @return array
|
$this->machineOpens = $machineOpens;
|
||||||
*/
|
}
|
||||||
public function asArray() {
|
|
||||||
|
public function getMachineOpens(): int {
|
||||||
|
return $this->machineOpens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asArray(): array {
|
||||||
return [
|
return [
|
||||||
'clicked' => (int)$this->clickCount,
|
'clicked' => $this->clickCount,
|
||||||
'opened' => (int)$this->openCount,
|
'opened' => $this->openCount,
|
||||||
'unsubscribed' => (int)$this->unsubscribeCount,
|
'machineOpens' => $this->machineOpens,
|
||||||
|
'unsubscribed' => $this->unsubscribeCount,
|
||||||
'revenue' => empty($this->wooCommerceRevenue) ? null : $this->wooCommerceRevenue->asArray(),
|
'revenue' => empty($this->wooCommerceRevenue) ? null : $this->wooCommerceRevenue->asArray(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
|
|||||||
use MailPoet\Entities\UserAgentEntity;
|
use MailPoet\Entities\UserAgentEntity;
|
||||||
use MailPoet\WooCommerce\Helper as WCHelper;
|
use MailPoet\WooCommerce\Helper as WCHelper;
|
||||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||||
|
use MailPoetVendor\Doctrine\ORM\QueryBuilder;
|
||||||
use MailPoetVendor\Doctrine\ORM\UnexpectedResultException;
|
use MailPoetVendor\Doctrine\ORM\UnexpectedResultException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,13 +33,15 @@ class NewsletterStatisticsRepository extends Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getStatistics(NewsletterEntity $newsletter): NewsletterStatistics {
|
public function getStatistics(NewsletterEntity $newsletter): NewsletterStatistics {
|
||||||
return new NewsletterStatistics(
|
$stats = new NewsletterStatistics(
|
||||||
$this->getStatisticsClickCount($newsletter),
|
$this->getStatisticsClickCount($newsletter),
|
||||||
$this->getStatisticsOpenCount($newsletter),
|
$this->getStatisticsOpenCount($newsletter),
|
||||||
$this->getStatisticsUnsubscribeCount($newsletter),
|
$this->getStatisticsUnsubscribeCount($newsletter),
|
||||||
$this->getTotalSentCount($newsletter),
|
$this->getTotalSentCount($newsletter),
|
||||||
$this->getWooCommerceRevenue($newsletter)
|
$this->getWooCommerceRevenue($newsletter)
|
||||||
);
|
);
|
||||||
|
$stats->setMachineOpens($this->getStatisticsMachineOpenCount($newsletter));
|
||||||
|
return $stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,6 +84,17 @@ class NewsletterStatisticsRepository extends Repository {
|
|||||||
return $counts[$newsletter->getId()] ?? 0;
|
return $counts[$newsletter->getId()] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStatisticsMachineOpenCount(NewsletterEntity $newsletter): int {
|
||||||
|
$qb = $this->getStatisticsQuery(StatisticsOpenEntity::class, [$newsletter]);
|
||||||
|
$result = $qb->andWhere('(stats.userAgentType = :userAgentType)')
|
||||||
|
->setParameter('userAgentType', UserAgentEntity::USER_AGENT_TYPE_MACHINE)
|
||||||
|
->getQuery()
|
||||||
|
->getResult();
|
||||||
|
|
||||||
|
if (empty($result)) return 0;
|
||||||
|
return $result['cnt'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
public function getStatisticsUnsubscribeCount(NewsletterEntity $newsletter): int {
|
public function getStatisticsUnsubscribeCount(NewsletterEntity $newsletter): int {
|
||||||
$counts = $this->getStatisticCounts(StatisticsUnsubscribeEntity::class, [$newsletter]);
|
$counts = $this->getStatisticCounts(StatisticsUnsubscribeEntity::class, [$newsletter]);
|
||||||
return $counts[$newsletter->getId()] ?? 0;
|
return $counts[$newsletter->getId()] ?? 0;
|
||||||
@ -132,12 +146,7 @@ class NewsletterStatisticsRepository extends Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getStatisticCounts(string $statisticsEntityName, array $newsletters): array {
|
private function getStatisticCounts(string $statisticsEntityName, array $newsletters): array {
|
||||||
$qb = $this->entityManager->createQueryBuilder()
|
$qb = $this->getStatisticsQuery($statisticsEntityName, $newsletters);
|
||||||
->select('IDENTITY(stats.newsletter) AS id, COUNT(DISTINCT stats.subscriber) as cnt')
|
|
||||||
->from($statisticsEntityName, 'stats')
|
|
||||||
->where('stats.newsletter IN (:newsletters)')
|
|
||||||
->groupBy('stats.newsletter')
|
|
||||||
->setParameter('newsletters', $newsletters);
|
|
||||||
if (in_array($statisticsEntityName, [StatisticsOpenEntity::class, StatisticsClickEntity::class], true)) {
|
if (in_array($statisticsEntityName, [StatisticsOpenEntity::class, StatisticsClickEntity::class], true)) {
|
||||||
$qb->andWhere('(stats.userAgentType = :userAgentType) OR (stats.userAgentType IS NULL)')
|
$qb->andWhere('(stats.userAgentType = :userAgentType) OR (stats.userAgentType IS NULL)')
|
||||||
->setParameter('userAgentType', UserAgentEntity::USER_AGENT_TYPE_HUMAN);
|
->setParameter('userAgentType', UserAgentEntity::USER_AGENT_TYPE_HUMAN);
|
||||||
@ -154,6 +163,15 @@ class NewsletterStatisticsRepository extends Repository {
|
|||||||
return $counts;
|
return $counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getStatisticsQuery(string $statisticsEntityName, array $newsletters): QueryBuilder {
|
||||||
|
return $this->entityManager->createQueryBuilder()
|
||||||
|
->select('IDENTITY(stats.newsletter) AS id, COUNT(DISTINCT stats.subscriber) as cnt')
|
||||||
|
->from($statisticsEntityName, 'stats')
|
||||||
|
->where('stats.newsletter IN (:newsletters)')
|
||||||
|
->groupBy('stats.newsletter')
|
||||||
|
->setParameter('newsletters', $newsletters);
|
||||||
|
}
|
||||||
|
|
||||||
private function getWooCommerceRevenues(array $newsletters) {
|
private function getWooCommerceRevenues(array $newsletters) {
|
||||||
if (!$this->wcHelper->isWooCommerceActive()) {
|
if (!$this->wcHelper->isWooCommerceActive()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -27,13 +27,16 @@ class NewslettersResponseBuilderTest extends \MailPoetTest {
|
|||||||
'opened' => 6,
|
'opened' => 6,
|
||||||
'clicked' => 4,
|
'clicked' => 4,
|
||||||
'unsubscribed' => 2,
|
'unsubscribed' => 2,
|
||||||
|
'machineOpens' => 9,
|
||||||
'revenue' => null,
|
'revenue' => null,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
$statistics = new NewsletterStatistics(4, 6, 2, 10, null);
|
||||||
|
$statistics->setMachineOpens(9);
|
||||||
$newsletterStatsRepository = Stub::make(NewsletterStatisticsRepository::class, [
|
$newsletterStatsRepository = Stub::make(NewsletterStatisticsRepository::class, [
|
||||||
'getTotalSentCount' => $stats['total_sent'],
|
'getTotalSentCount' => $stats['total_sent'],
|
||||||
'getChildrenCount' => $stats['children_count'],
|
'getChildrenCount' => $stats['children_count'],
|
||||||
'getStatistics' => new NewsletterStatistics(4, 6, 2, 10, null),
|
'getStatistics' => $statistics,
|
||||||
]);
|
]);
|
||||||
$newsletterRepository = Stub::make(NewslettersRepository::class);
|
$newsletterRepository = Stub::make(NewslettersRepository::class);
|
||||||
$newsletterUrl = $this->diContainer->get(Url::class);
|
$newsletterUrl = $this->diContainer->get(Url::class);
|
||||||
|
@ -389,6 +389,7 @@
|
|||||||
'statsReplyToAddress': __('Reply-to'),
|
'statsReplyToAddress': __('Reply-to'),
|
||||||
'statsTotalSent': __('Sent to'),
|
'statsTotalSent': __('Sent to'),
|
||||||
'percentageOpened': _x('opened', 'Percentage of subscribers that opened a newsletter link'),
|
'percentageOpened': _x('opened', 'Percentage of subscribers that opened a newsletter link'),
|
||||||
|
'percentageMachineOpened': _x('machine-opened', 'Percentage of newsletters that were opened by a machine'),
|
||||||
'percentageClicked': _x('clicked', 'Percentage of subscribers that clicked a newsletter link'),
|
'percentageClicked': _x('clicked', 'Percentage of subscribers that clicked a newsletter link'),
|
||||||
'percentageUnsubscribed': _x('unsubscribed', 'Percentage of subscribers that unsubscribed from a newsletter'),
|
'percentageUnsubscribed': _x('unsubscribed', 'Percentage of subscribers that unsubscribed from a newsletter'),
|
||||||
'readMoreOnStats': __('Read more on stats.'),
|
'readMoreOnStats': __('Read more on stats.'),
|
||||||
|
Reference in New Issue
Block a user