mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 12:11:19 +03:00
Allow filtering by date in VisitIterationRepository
This commit is contained in:
parent
13ee71f351
commit
ce0f61b66d
6 changed files with 54 additions and 16 deletions
|
@ -73,7 +73,7 @@ return [
|
|||
Visit\Geolocation\VisitLocator::class => ConfigAbstractFactory::class,
|
||||
Visit\Geolocation\VisitToLocationHelper::class => ConfigAbstractFactory::class,
|
||||
Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class,
|
||||
Visit\Repository\VisitLocationRepository::class => [
|
||||
Visit\Repository\VisitIterationRepository::class => [
|
||||
EntityRepositoryFactory::class,
|
||||
Visit\Entity\Visit::class,
|
||||
],
|
||||
|
@ -146,7 +146,7 @@ return [
|
|||
ShortUrl\Repository\ShortUrlListRepository::class,
|
||||
Options\UrlShortenerOptions::class,
|
||||
],
|
||||
Visit\Geolocation\VisitLocator::class => ['em', Visit\Repository\VisitLocationRepository::class],
|
||||
Visit\Geolocation\VisitLocator::class => ['em', Visit\Repository\VisitIterationRepository::class],
|
||||
Visit\Geolocation\VisitToLocationHelper::class => [IpLocationResolverInterface::class],
|
||||
Visit\VisitsStatsHelper::class => ['em'],
|
||||
Tag\TagService::class => ['em'],
|
||||
|
|
|
@ -8,14 +8,14 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitIterationRepositoryInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
|
||||
class VisitLocator implements VisitLocatorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly VisitLocationRepositoryInterface $repo,
|
||||
private readonly VisitIterationRepositoryInterface $repo,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,14 @@ namespace Shlinkio\Shlink\Core\Visit\Repository;
|
|||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
|
||||
class VisitLocationRepository extends EntitySpecificationRepository implements VisitLocationRepositoryInterface
|
||||
/**
|
||||
* Allows iterating large amounts of visits in a memory-efficient way, to use in batch processes
|
||||
*/
|
||||
class VisitIterationRepository extends EntitySpecificationRepository implements VisitIterationRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @return iterable<Visit>
|
||||
|
@ -42,9 +47,18 @@ class VisitLocationRepository extends EntitySpecificationRepository implements V
|
|||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
public function findAllVisits(?DateRange $dateRange = null, int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->createQueryBuilder('v');
|
||||
if ($dateRange?->startDate !== null) {
|
||||
$qb->andWhere($qb->expr()->gte('v.date', ':since'));
|
||||
$qb->setParameter('since', $dateRange->startDate, ChronosDateTimeType::CHRONOS_DATETIME);
|
||||
}
|
||||
if ($dateRange?->endDate !== null) {
|
||||
$qb->andWhere($qb->expr()->lte('v.date', ':until'));
|
||||
$qb->setParameter('until', $dateRange->endDate, ChronosDateTimeType::CHRONOS_DATETIME);
|
||||
}
|
||||
|
||||
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||
}
|
||||
|
|
@ -4,9 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Visit\Repository;
|
||||
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
|
||||
interface VisitLocationRepositoryInterface
|
||||
interface VisitIterationRepositoryInterface
|
||||
{
|
||||
public const DEFAULT_BLOCK_SIZE = 10000;
|
||||
|
||||
|
@ -23,5 +24,5 @@ interface VisitLocationRepositoryInterface
|
|||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
public function findAllVisits(?DateRange $dateRange = null, int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
}
|
|
@ -4,27 +4,29 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkioDbTest\Shlink\Core\Visit\Repository;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepository;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitIterationRepository;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
||||
|
||||
use function array_map;
|
||||
use function range;
|
||||
|
||||
class VisitLocationRepositoryTest extends DatabaseTestCase
|
||||
class VisitIterationRepositoryTest extends DatabaseTestCase
|
||||
{
|
||||
private VisitLocationRepository $repo;
|
||||
private VisitIterationRepository $repo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$this->repo = new VisitLocationRepository($em, $em->getClassMetadata(Visit::class));
|
||||
$this->repo = new VisitIterationRepository($em, $em->getClassMetadata(Visit::class));
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideBlockSize')]
|
||||
|
@ -33,7 +35,9 @@ class VisitLocationRepositoryTest extends DatabaseTestCase
|
|||
$shortUrl = ShortUrl::createFake();
|
||||
$this->getEntityManager()->persist($shortUrl);
|
||||
|
||||
$unmodifiedDate = Chronos::now();
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
Chronos::setTestNow($unmodifiedDate->subDays($i)); // Enforce a different day for every visit
|
||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
|
||||
|
||||
if ($i >= 2) {
|
||||
|
@ -44,15 +48,34 @@ class VisitLocationRepositoryTest extends DatabaseTestCase
|
|||
|
||||
$this->getEntityManager()->persist($visit);
|
||||
}
|
||||
Chronos::setTestNow();
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
$withEmptyLocation = $this->repo->findVisitsWithEmptyLocation($blockSize);
|
||||
$unlocated = $this->repo->findUnlocatedVisits($blockSize);
|
||||
$all = $this->repo->findAllVisits($blockSize);
|
||||
$all = $this->repo->findAllVisits(blockSize: $blockSize);
|
||||
$lastThreeDays = $this->repo->findAllVisits(
|
||||
dateRange: DateRange::since(Chronos::now()->subDays(2)),
|
||||
blockSize: $blockSize,
|
||||
);
|
||||
$firstTwoDays = $this->repo->findAllVisits(
|
||||
dateRange: DateRange::until(Chronos::now()->subDays(4)),
|
||||
blockSize: $blockSize,
|
||||
);
|
||||
$daysInBetween = $this->repo->findAllVisits(
|
||||
dateRange: DateRange::between(
|
||||
startDate: Chronos::now()->subDays(5),
|
||||
endDate: Chronos::now()->subDays(2),
|
||||
),
|
||||
blockSize: $blockSize,
|
||||
);
|
||||
|
||||
self::assertCount(2, [...$unlocated]);
|
||||
self::assertCount(4, [...$withEmptyLocation]);
|
||||
self::assertCount(6, [...$all]);
|
||||
self::assertCount(3, [...$lastThreeDays]);
|
||||
self::assertCount(2, [...$firstTwoDays]);
|
||||
self::assertCount(4, [...$daysInBetween]);
|
||||
}
|
||||
|
||||
public static function provideBlockSize(): iterable
|
|
@ -17,7 +17,7 @@ use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
|||
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitGeolocationHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocator;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitIterationRepositoryInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
|
||||
use function array_map;
|
||||
|
@ -30,12 +30,12 @@ class VisitLocatorTest extends TestCase
|
|||
{
|
||||
private VisitLocator $visitService;
|
||||
private MockObject & EntityManager $em;
|
||||
private MockObject & VisitLocationRepositoryInterface $repo;
|
||||
private MockObject & VisitIterationRepositoryInterface $repo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->em = $this->createMock(EntityManager::class);
|
||||
$this->repo = $this->createMock(VisitLocationRepositoryInterface::class);
|
||||
$this->repo = $this->createMock(VisitIterationRepositoryInterface::class);
|
||||
|
||||
$this->visitService = new VisitLocator($this->em, $this->repo);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue