mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-11 09:43:13 +08:00
Support filtering orphan visits by type in VisitRepository
This commit is contained in:
12
module/Core/src/Visit/Model/OrphanVisitType.php
Normal file
12
module/Core/src/Visit/Model/OrphanVisitType.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Visit\Model;
|
||||
|
||||
enum OrphanVisitType: string
|
||||
{
|
||||
case INVALID_SHORT_URL = 'invalid_short_url';
|
||||
case BASE_URL = 'base_url';
|
||||
case REGULAR_404 = 'regular_404';
|
||||
}
|
||||
@@ -8,7 +8,7 @@ enum VisitType: string
|
||||
{
|
||||
case VALID_SHORT_URL = 'valid_short_url';
|
||||
case IMPORTED = 'imported';
|
||||
case INVALID_SHORT_URL = 'invalid_short_url';
|
||||
case BASE_URL = 'base_url';
|
||||
case REGULAR_404 = 'regular_404';
|
||||
case INVALID_SHORT_URL = OrphanVisitType::INVALID_SHORT_URL->value;
|
||||
case BASE_URL = OrphanVisitType::BASE_URL->value;
|
||||
case REGULAR_404 = OrphanVisitType::REGULAR_404->value;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace Shlinkio\Shlink\Core\Visit\Paginator\Adapter;
|
||||
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\AbstractCacheableCountPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsListFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
@@ -22,7 +22,7 @@ class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
||||
|
||||
protected function doCount(): int
|
||||
{
|
||||
return $this->repo->countOrphanVisits(new VisitsCountFiltering(
|
||||
return $this->repo->countOrphanVisits(new OrphanVisitsCountFiltering(
|
||||
dateRange: $this->params->dateRange,
|
||||
excludeBots: $this->params->excludeBots,
|
||||
apiKey: $this->apiKey,
|
||||
@@ -31,7 +31,7 @@ class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
||||
|
||||
public function getSlice(int $offset, int $length): iterable
|
||||
{
|
||||
return $this->repo->findOrphanVisits(new VisitsListFiltering(
|
||||
return $this->repo->findOrphanVisits(new OrphanVisitsListFiltering(
|
||||
dateRange: $this->params->dateRange,
|
||||
excludeBots: $this->params->excludeBots,
|
||||
apiKey: $this->apiKey,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Visit\Persistence;
|
||||
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitType;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
class OrphanVisitsCountFiltering extends VisitsCountFiltering
|
||||
{
|
||||
public function __construct(
|
||||
?DateRange $dateRange = null,
|
||||
bool $excludeBots = false,
|
||||
?ApiKey $apiKey = null,
|
||||
public readonly ?OrphanVisitType $type = null,
|
||||
) {
|
||||
parent::__construct($dateRange, $excludeBots, $apiKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Visit\Persistence;
|
||||
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitType;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
final class OrphanVisitsListFiltering extends OrphanVisitsCountFiltering
|
||||
{
|
||||
public function __construct(
|
||||
?DateRange $dateRange = null,
|
||||
bool $excludeBots = false,
|
||||
?ApiKey $apiKey = null,
|
||||
?OrphanVisitType $type = null,
|
||||
public readonly ?int $limit = null,
|
||||
public readonly ?int $offset = null,
|
||||
) {
|
||||
parent::__construct($dateRange, $excludeBots, $apiKey, $type);
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,4 @@ class VisitsCountFiltering
|
||||
public readonly ?ApiKey $apiKey = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function withApiKey(?ApiKey $apiKey): self
|
||||
{
|
||||
return new self(apiKey: $apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsListFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Spec\CountOfNonOrphanVisits;
|
||||
@@ -138,7 +140,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function findOrphanVisits(VisitsListFiltering $filtering): array
|
||||
public function findOrphanVisits(OrphanVisitsListFiltering $filtering): array
|
||||
{
|
||||
if ($filtering->apiKey?->hasRole(Role::NO_ORPHAN_VISITS)) {
|
||||
return [];
|
||||
@@ -146,10 +148,17 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
||||
|
||||
$qb = $this->createAllVisitsQueryBuilder($filtering);
|
||||
$qb->andWhere($qb->expr()->isNull('v.shortUrl'));
|
||||
|
||||
// Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later
|
||||
if ($filtering->type) {
|
||||
$conn = $this->getEntityManager()->getConnection();
|
||||
$qb->andWhere($qb->expr()->eq('v.type', $conn->quote($filtering->type->value)));
|
||||
}
|
||||
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countOrphanVisits(VisitsCountFiltering $filtering): int
|
||||
public function countOrphanVisits(OrphanVisitsCountFiltering $filtering): int
|
||||
{
|
||||
if ($filtering->apiKey?->hasRole(Role::NO_ORPHAN_VISITS)) {
|
||||
return 0;
|
||||
@@ -176,7 +185,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
||||
return (int) $this->matchSingleScalarResult(new CountOfNonOrphanVisits($filtering));
|
||||
}
|
||||
|
||||
private function createAllVisitsQueryBuilder(VisitsListFiltering $filtering): QueryBuilder
|
||||
private function createAllVisitsQueryBuilder(VisitsListFiltering|OrphanVisitsListFiltering $filtering): QueryBuilder
|
||||
{
|
||||
// Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later
|
||||
// Since they are not provided by the caller, it's reasonably safe
|
||||
|
||||
@@ -8,6 +8,8 @@ use Doctrine\Persistence\ObjectRepository;
|
||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsListFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
||||
|
||||
@@ -37,9 +39,9 @@ interface VisitRepositoryInterface extends ObjectRepository, EntitySpecification
|
||||
/**
|
||||
* @return Visit[]
|
||||
*/
|
||||
public function findOrphanVisits(VisitsListFiltering $filtering): array;
|
||||
public function findOrphanVisits(OrphanVisitsListFiltering $filtering): array;
|
||||
|
||||
public function countOrphanVisits(VisitsCountFiltering $filtering): int;
|
||||
public function countOrphanVisits(OrphanVisitsCountFiltering $filtering): int;
|
||||
|
||||
/**
|
||||
* @return Visit[]
|
||||
|
||||
@@ -8,11 +8,11 @@ use Happyr\DoctrineSpecification\Spec;
|
||||
use Happyr\DoctrineSpecification\Specification\BaseSpecification;
|
||||
use Happyr\DoctrineSpecification\Specification\Specification;
|
||||
use Shlinkio\Shlink\Core\Spec\InDateRange;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsCountFiltering;
|
||||
|
||||
class CountOfOrphanVisits extends BaseSpecification
|
||||
{
|
||||
public function __construct(private VisitsCountFiltering $filtering)
|
||||
public function __construct(private readonly OrphanVisitsCountFiltering $filtering)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -28,6 +28,10 @@ class CountOfOrphanVisits extends BaseSpecification
|
||||
$conditions[] = Spec::eq('potentialBot', false);
|
||||
}
|
||||
|
||||
if ($this->filtering->type) {
|
||||
$conditions[] = Spec::eq('type', $this->filtering->type->value);
|
||||
}
|
||||
|
||||
return Spec::countOf(Spec::andX(...$conditions));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use Shlinkio\Shlink\Core\Visit\Paginator\Adapter\NonOrphanVisitsPaginatorAdapter
|
||||
use Shlinkio\Shlink\Core\Visit\Paginator\Adapter\OrphanVisitsPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Visit\Paginator\Adapter\ShortUrlVisitsPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Visit\Paginator\Adapter\TagVisitsPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepository;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface;
|
||||
@@ -42,13 +43,13 @@ class VisitsStatsHelper implements VisitsStatsHelperInterface
|
||||
$visitsRepo = $this->em->getRepository(Visit::class);
|
||||
|
||||
return new VisitsStats(
|
||||
nonOrphanVisitsTotal: $visitsRepo->countNonOrphanVisits(VisitsCountFiltering::withApiKey($apiKey)),
|
||||
orphanVisitsTotal: $visitsRepo->countOrphanVisits(VisitsCountFiltering::withApiKey($apiKey)),
|
||||
nonOrphanVisitsTotal: $visitsRepo->countNonOrphanVisits(new VisitsCountFiltering(apiKey: $apiKey)),
|
||||
orphanVisitsTotal: $visitsRepo->countOrphanVisits(new OrphanVisitsCountFiltering(apiKey: $apiKey)),
|
||||
nonOrphanVisitsNonBots: $visitsRepo->countNonOrphanVisits(
|
||||
new VisitsCountFiltering(excludeBots: true, apiKey: $apiKey),
|
||||
),
|
||||
orphanVisitsNonBots: $visitsRepo->countOrphanVisits(
|
||||
new VisitsCountFiltering(excludeBots: true, apiKey: $apiKey),
|
||||
new OrphanVisitsCountFiltering(excludeBots: true, apiKey: $apiKey),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user