Skip to content

Commit 23a5169

Browse files
x86demonvitaliyberdylo
authored andcommitted
CRM-7972: Slow Ecommerce widget leads to 40 seconds login (#9087)
1 parent dafc4d4 commit 23a5169

9 files changed

+770
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
namespace Oro\Bridge\MarketingCRM\Provider;
4+
5+
use Doctrine\Common\Persistence\ManagerRegistry;
6+
use Doctrine\DBAL\Connection;
7+
use Doctrine\ORM\EntityRepository;
8+
use Doctrine\ORM\NoResultException;
9+
use Doctrine\ORM\QueryBuilder;
10+
use Oro\Bundle\ConfigBundle\Config\ConfigManager;
11+
use Oro\Bundle\FeatureToggleBundle\Checker\FeatureCheckerHolderTrait;
12+
use Oro\Bundle\FeatureToggleBundle\Checker\FeatureToggleableInterface;
13+
use Oro\Bundle\SecurityBundle\ORM\Walker\AclHelper;
14+
use Oro\Bundle\TrackingBundle\Entity\UniqueTrackingVisit;
15+
16+
class AbstractPrecalculatedVisitProvider implements FeatureToggleableInterface
17+
{
18+
use FeatureCheckerHolderTrait;
19+
20+
/**
21+
* @var ManagerRegistry
22+
*/
23+
private $registry;
24+
25+
/**
26+
* @var AclHelper
27+
*/
28+
private $aclHelper;
29+
30+
/**
31+
* @var ConfigManager
32+
*/
33+
private $configManager;
34+
35+
/**
36+
* @param ManagerRegistry $registry
37+
* @param ConfigManager $configManager
38+
* @param AclHelper $aclHelper
39+
*/
40+
public function __construct(
41+
ManagerRegistry $registry,
42+
ConfigManager $configManager,
43+
AclHelper $aclHelper
44+
) {
45+
$this->registry = $registry;
46+
$this->aclHelper = $aclHelper;
47+
$this->configManager = $configManager;
48+
}
49+
50+
/**
51+
* @param QueryBuilder $queryBuilder
52+
* @return int
53+
*/
54+
protected function getSingleIntegerResult(QueryBuilder $queryBuilder)
55+
{
56+
try {
57+
return (int)$this->aclHelper->apply($queryBuilder)->getSingleScalarResult();
58+
} catch (NoResultException $ex) {
59+
return 0;
60+
}
61+
}
62+
63+
/**
64+
* @param QueryBuilder $queryBuilder
65+
* @param \DateTime $from
66+
* @param \DateTime $to
67+
*/
68+
protected function applyDateLimit(QueryBuilder $queryBuilder, \DateTime $from, \DateTime $to)
69+
{
70+
if ($from && $to && $this->getDate($from) === $this->getDate($to)) {
71+
$queryBuilder->andWhere($queryBuilder->expr()->eq('t.actionDate', ':date'))
72+
->setParameter('date', $this->getDate($from));
73+
} else {
74+
if ($from) {
75+
$queryBuilder
76+
->andWhere($queryBuilder->expr()->gte('t.actionDate', ':from'))
77+
->setParameter('from', $this->getDate($from));
78+
}
79+
if ($to) {
80+
$queryBuilder
81+
->andWhere($queryBuilder->expr()->lte('t.actionDate', ':to'))
82+
->setParameter('to', $this->getDate($to));
83+
}
84+
}
85+
}
86+
87+
/**
88+
* @return QueryBuilder
89+
*/
90+
protected function createUniqueVisitQueryBuilder()
91+
{
92+
$queryBuilder = $this
93+
->getUniqueTrackingVisitRepository()
94+
->createQueryBuilder('t');
95+
96+
return $queryBuilder;
97+
}
98+
99+
/**
100+
* @return bool
101+
*/
102+
protected function isPrecalculatedStatisticEnabled()
103+
{
104+
return $this->configManager->get('oro_tracking.precalculated_statistic_enabled');
105+
}
106+
107+
/**
108+
* @return EntityRepository
109+
*/
110+
private function getUniqueTrackingVisitRepository()
111+
{
112+
return $this->registry->getManagerForClass(UniqueTrackingVisit::class)
113+
->getRepository(UniqueTrackingVisit::class);
114+
}
115+
116+
/**
117+
* @param \DateTime $dateTime
118+
* @return string
119+
*/
120+
private function getDate(\DateTime $dateTime)
121+
{
122+
/** @var Connection $connection */
123+
$connection = $this->registry->getConnection();
124+
$dateFormat = $connection->getDatabasePlatform()->getDateFormatString();
125+
126+
return $dateTime->format($dateFormat);
127+
}
128+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
namespace Oro\Bridge\MarketingCRM\Provider;
4+
5+
use Doctrine\ORM\QueryBuilder;
6+
use Oro\Bundle\ChannelBundle\Entity\Channel;
7+
use Oro\Bundle\MagentoBundle\Entity\Customer;
8+
use Oro\Bundle\MagentoBundle\Provider\ChannelType;
9+
use Oro\Bundle\MagentoBundle\Provider\TrackingVisitProviderInterface;
10+
11+
class PrecalculatedTrackingVisitProvider extends AbstractPrecalculatedVisitProvider implements
12+
TrackingVisitProviderInterface
13+
{
14+
/**
15+
* @var TrackingVisitProviderInterface
16+
*/
17+
private $trackingVisitProvider;
18+
19+
/**
20+
* @param TrackingVisitProviderInterface $trackingVisitProvider
21+
*/
22+
public function setVisitProvider(TrackingVisitProviderInterface $trackingVisitProvider)
23+
{
24+
$this->trackingVisitProvider = $trackingVisitProvider;
25+
}
26+
27+
/**
28+
* Return total number of visits, last visit date and visits per month
29+
* filtered by customers
30+
*
31+
* @param Customer[] $customers
32+
*
33+
* @return array
34+
*/
35+
public function getAggregates(array $customers)
36+
{
37+
return $this->trackingVisitProvider->getAggregates($customers);
38+
}
39+
40+
/**
41+
* @param \DateTime $from
42+
* @param \DateTime $to
43+
*
44+
* @return int
45+
*/
46+
public function getDeeplyVisitedCount(\DateTime $from = null, \DateTime $to = null)
47+
{
48+
if (!$this->isPrecalculatedStatisticEnabled()) {
49+
return $this->trackingVisitProvider->getDeeplyVisitedCount($from, $to);
50+
}
51+
52+
if (!$this->isFeaturesEnabled()) {
53+
return 0;
54+
}
55+
56+
$queryBuilder = $this->getVisitCountQueryBuilder($from, $to);
57+
$queryBuilder->andHaving('COUNT(t.userIdentifier) > 1');
58+
59+
return $this->getSingleIntegerResult($queryBuilder);
60+
}
61+
62+
/**
63+
* @param \DateTime $from
64+
* @param \DateTime $to
65+
*
66+
* @return int
67+
*/
68+
public function getVisitedCount(\DateTime $from = null, \DateTime $to = null)
69+
{
70+
if (!$this->isPrecalculatedStatisticEnabled()) {
71+
return $this->trackingVisitProvider->getVisitedCount($from, $to);
72+
}
73+
74+
if (!$this->isFeaturesEnabled()) {
75+
return 0;
76+
}
77+
78+
$queryBuilder = $this->getVisitCountQueryBuilder($from, $to);
79+
80+
return $this->getSingleIntegerResult($queryBuilder);
81+
}
82+
83+
/**
84+
* @param \DateTime|null $from
85+
* @param \DateTime|null $to
86+
* @return QueryBuilder
87+
*/
88+
private function getVisitCountQueryBuilder(\DateTime $from = null, \DateTime $to = null)
89+
{
90+
$queryBuilder = $this->createUniqueVisitQueryBuilder();
91+
$this->applyDateLimit($queryBuilder, $from, $to);
92+
93+
$queryBuilder
94+
->select($queryBuilder->expr()->countDistinct('t.userIdentifier'))
95+
->join('t.trackingWebsite', 'tw')
96+
->join('tw.channel', 'c')
97+
->andWhere($queryBuilder->expr()->eq('c.channelType', ':channel'))
98+
->andWhere($queryBuilder->expr()->eq('c.status', ':status'))
99+
->setParameter('channel', ChannelType::TYPE)
100+
->setParameter('status', Channel::STATUS_ACTIVE);
101+
102+
return $queryBuilder;
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Oro\Bridge\MarketingCRM\Provider;
4+
5+
use Doctrine\ORM\QueryBuilder;
6+
use Oro\Bundle\ChannelBundle\Entity\Channel;
7+
use Oro\Bundle\MagentoBundle\Provider\ChannelType;
8+
use Oro\Bundle\MagentoBundle\Provider\WebsiteVisitProviderInterface;
9+
10+
class PrecalculatedWebsiteVisitProvider extends AbstractPrecalculatedVisitProvider implements
11+
WebsiteVisitProviderInterface
12+
{
13+
/**
14+
* @var WebsiteVisitProviderInterface
15+
*/
16+
private $visitProvider;
17+
18+
/**
19+
* @param WebsiteVisitProviderInterface $visitProvider
20+
*/
21+
public function setVisitProvider(WebsiteVisitProviderInterface $visitProvider)
22+
{
23+
$this->visitProvider = $visitProvider;
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function getSiteVisitsValues($dateRange)
30+
{
31+
if (!$this->isPrecalculatedStatisticEnabled()) {
32+
return $this->visitProvider->getSiteVisitsValues($dateRange);
33+
}
34+
35+
if (!$this->isFeaturesEnabled()) {
36+
return 0;
37+
}
38+
39+
$queryBuilder = $this->getVisitsCountQueryBuilder($dateRange['start'], $dateRange['end']);
40+
41+
return $this->getSingleIntegerResult($queryBuilder);
42+
}
43+
44+
/**
45+
* @param \DateTime|null $from
46+
* @param \DateTime|null $to
47+
* @return QueryBuilder
48+
*/
49+
private function getVisitsCountQueryBuilder(\DateTime $from = null, \DateTime $to = null)
50+
{
51+
$queryBuilder = $this->createUniqueVisitQueryBuilder();
52+
53+
$queryBuilder->select('SUM(t.visitCount)')
54+
->join('t.trackingWebsite', 'site')
55+
->leftJoin('site.channel', 'channel')
56+
->where($queryBuilder->expr()->orX(
57+
$queryBuilder->expr()->isNull('channel.id'),
58+
$queryBuilder->expr()->andX(
59+
$queryBuilder->expr()->eq('channel.channelType', ':channel'),
60+
$queryBuilder->expr()->eq('channel.status', ':status')
61+
)
62+
))
63+
->setParameter('channel', ChannelType::TYPE)
64+
->setParameter('status', Channel::STATUS_ACTIVE);
65+
66+
$this->applyDateLimit($queryBuilder, $from, $to);
67+
68+
return $queryBuilder;
69+
}
70+
}

Diff for: src/Oro/Bridge/MarketingCRM/Resources/config/services.yml

+28
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,31 @@ services:
2222
class: %oro_channel.listener.channel_repository_subscriber.class%
2323
tags:
2424
- { name: doctrine.event_subscriber }
25+
26+
oro_magento.provider.tracking_visit_precalculated.abstract:
27+
class: Oro\Bridge\MarketingCRM\Provider\AbstractPrecalculatedVisitProvider
28+
abstract: true
29+
arguments:
30+
- '@doctrine'
31+
- '@oro_config.global'
32+
- '@oro_security.acl_helper'
33+
34+
oro_magento.provider.tracking_visit.precalculated:
35+
class: Oro\Bridge\MarketingCRM\Provider\PrecalculatedTrackingVisitProvider
36+
parent: oro_magento.provider.tracking_visit_precalculated.abstract
37+
decorates: oro_magento.provider.tracking_visit
38+
public: false
39+
calls:
40+
- ['setVisitProvider', ['@oro_magento.provider.tracking_visit.precalculated.inner']]
41+
tags:
42+
- { name: oro_featuretogle.feature, feature: 'tracking' }
43+
44+
oro_magento.provider.website_visit.precalculated:
45+
class: Oro\Bridge\MarketingCRM\Provider\PrecalculatedWebsiteVisitProvider
46+
parent: oro_magento.provider.tracking_visit_precalculated.abstract
47+
decorates: oro_magento.provider.website_visit
48+
public: false
49+
calls:
50+
- ['setVisitProvider', ['@oro_magento.provider.website_visit.precalculated.inner']]
51+
tags:
52+
- { name: oro_featuretogle.feature, feature: 'tracking' }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Oro\Bridge\MarketingCRM\Tests\Functional\DataFixtures;
4+
5+
use Doctrine\Common\DataFixtures\AbstractFixture;
6+
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7+
use Doctrine\Common\Persistence\ObjectManager;
8+
use Oro\Bundle\ChannelBundle\Entity\Channel;
9+
use Oro\Bundle\MagentoBundle\Tests\Functional\Fixture\LoadMagentoChannel;
10+
use Oro\Bundle\TrackingBundle\Entity\TrackingWebsite;
11+
use Oro\Bundle\TrackingBundle\Tests\Functional\DataFixtures\LoadTrackingWebsites;
12+
13+
class LoadTrackingWebsiteToMagentoChannel extends AbstractFixture implements DependentFixtureInterface
14+
{
15+
/**
16+
* {@inheritdoc}
17+
*/
18+
public function getDependencies()
19+
{
20+
return [
21+
LoadTrackingWebsites::class,
22+
LoadMagentoChannel::class
23+
];
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function load(ObjectManager $manager)
30+
{
31+
/** @var TrackingWebsite $website */
32+
$website = $this->getReference(LoadTrackingWebsites::TRACKING_WEBSITE);
33+
34+
if (method_exists($website, 'setChannel')) {
35+
/** @var Channel $channel */
36+
$channel = $this->getReference('default_channel');
37+
$website->setChannel($channel);
38+
$manager->flush($website);
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)