Added support for a new tagsMode param when listing short URLs

This commit is contained in:
Alejandro Celaya 2022-01-04 12:11:47 +01:00
parent d0daeb0078
commit 103af2e2c1
7 changed files with 64 additions and 14 deletions

View file

@ -14,11 +14,15 @@ use function Shlinkio\Shlink\Core\parseDateField;
final class ShortUrlsParams
{
public const DEFAULT_ITEMS_PER_PAGE = 10;
public const TAGS_MODE_ANY = 'any';
public const TAGS_MODE_ALL = 'all';
private int $page;
private int $itemsPerPage;
private ?string $searchTerm;
private array $tags;
/** @var self::TAGS_MODE_ANY|self::TAGS_MODE_ALL */
private string $tagsMode = self::TAGS_MODE_ANY;
private ShortUrlsOrdering $orderBy;
private ?DateRange $dateRange;
@ -63,6 +67,7 @@ final class ShortUrlsParams
$this->itemsPerPage = (int) (
$inputFilter->getValue(ShortUrlsParamsInputFilter::ITEMS_PER_PAGE) ?? self::DEFAULT_ITEMS_PER_PAGE
);
$this->tagsMode = $inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE) ?? self::TAGS_MODE_ANY;
}
public function page(): int
@ -94,4 +99,12 @@ final class ShortUrlsParams
{
return $this->dateRange;
}
/**
* @return self::TAGS_MODE_ANY|self::TAGS_MODE_ALL
*/
public function tagsMode(): string
{
return $this->tagsMode;
}
}

View file

@ -25,6 +25,7 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
$offset,
$this->params->searchTerm(),
$this->params->tags(),
$this->params->tagsMode(),
$this->params->orderBy(),
$this->params->dateRange(),
$this->apiKey?->spec(),
@ -36,6 +37,7 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
return $this->repository->countList(
$this->params->searchTerm(),
$this->params->tags(),
$this->params->tagsMode(),
$this->params->dateRange(),
$this->apiKey?->spec(),
);

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
@ -15,6 +16,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use function array_column;
@ -32,11 +34,12 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
?int $offset = null,
?string $searchTerm = null,
array $tags = [],
string $tagsMode = ShortUrlsParams::TAGS_MODE_ANY,
?ShortUrlsOrdering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): array {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange, $spec);
$qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec);
$qb->select('DISTINCT s')
->setMaxResults($limit)
->setFirstResult($offset);
@ -77,10 +80,11 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
public function countList(
?string $searchTerm = null,
array $tags = [],
string $tagsMode = ShortUrlsParams::TAGS_MODE_ANY,
?DateRange $dateRange = null,
?Specification $spec = null,
): int {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange, $spec);
$qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec);
$qb->select('COUNT(DISTINCT s)');
return (int) $qb->getQuery()->getSingleScalarResult();
@ -89,6 +93,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
private function createListQueryBuilder(
?string $searchTerm,
array $tags,
string $tagsMode,
?DateRange $dateRange,
?Specification $spec,
): QueryBuilder {
@ -139,8 +144,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
{
// When ordering DESC, Postgres puts nulls at the beginning while the rest of supported DB engines put them at
// the bottom
$dbPlatform = $this->getEntityManager()->getConnection()->getDatabasePlatform()->getName();
$ordering = $dbPlatform === 'postgresql' ? 'ASC' : 'DESC';
$dbPlatform = $this->getEntityManager()->getConnection()->getDatabasePlatform();
$ordering = $dbPlatform instanceof PostgreSQLPlatform ? 'ASC' : 'DESC';
$dql = <<<DQL
SELECT s

View file

@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface
@ -21,6 +22,7 @@ interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificat
?int $offset = null,
?string $searchTerm = null,
array $tags = [],
string $tagsMode = ShortUrlsParams::TAGS_MODE_ANY,
?ShortUrlsOrdering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null,
@ -29,6 +31,7 @@ interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificat
public function countList(
?string $searchTerm = null,
array $tags = [],
string $tagsMode = ShortUrlsParams::TAGS_MODE_ANY,
?DateRange $dateRange = null,
?Specification $spec = null,
): int;

View file

@ -5,8 +5,10 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Validation;
use Laminas\InputFilter\InputFilter;
use Laminas\Validator\InArray;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Common\Validation;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
class ShortUrlsParamsInputFilter extends InputFilter
{
@ -18,6 +20,7 @@ class ShortUrlsParamsInputFilter extends InputFilter
public const START_DATE = 'startDate';
public const END_DATE = 'endDate';
public const ITEMS_PER_PAGE = 'itemsPerPage';
public const TAGS_MODE = 'tagsMode';
public function __construct(array $data)
{
@ -36,5 +39,12 @@ class ShortUrlsParamsInputFilter extends InputFilter
$this->add($this->createNumericInput(self::ITEMS_PER_PAGE, false, Paginator::ALL_ITEMS));
$this->add($this->createTagsInput(self::TAGS, false));
$tagsMode = $this->createInput(self::TAGS_MODE, false);
$tagsMode->getValidatorChain()->attach(new InArray([
'haystack' => [ShortUrlsParams::TAGS_MODE_ALL, ShortUrlsParams::TAGS_MODE_ANY],
'strict' => InArray::COMPARE_STRICT,
]));
$this->add($tagsMode);
}
}

View file

@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver;
@ -127,22 +128,31 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
self::assertCount(1, $this->repo->findList(2, 2));
$result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([
$tagsModeAll = ShortUrlsParams::TAGS_MODE_ANY;
$result = $this->repo->findList(null, null, null, [], $tagsModeAll, ShortUrlsOrdering::fromRawData([
'orderBy' => 'visits-DESC',
]));
self::assertCount(3, $result);
self::assertSame($bar, $result[0]);
$result = $this->repo->findList(null, null, null, [], null, DateRange::withEndDate(Chronos::now()->subDays(2)));
$result = $this->repo->findList(null, null, null, [], $tagsModeAll, null, DateRange::withEndDate(
Chronos::now()->subDays(2),
));
self::assertCount(1, $result);
self::assertEquals(1, $this->repo->countList(null, [], DateRange::withEndDate(Chronos::now()->subDays(2))));
self::assertEquals(1, $this->repo->countList(null, [], $tagsModeAll, DateRange::withEndDate(
Chronos::now()->subDays(2),
)));
self::assertSame($foo2, $result[0]);
self::assertCount(
2,
$this->repo->findList(null, null, null, [], null, DateRange::withStartDate(Chronos::now()->subDays(2))),
$this->repo->findList(null, null, null, [], $tagsModeAll, null, DateRange::withStartDate(
Chronos::now()->subDays(2),
)),
);
self::assertEquals(2, $this->repo->countList(null, [], DateRange::withStartDate(Chronos::now()->subDays(2))));
self::assertEquals(2, $this->repo->countList(null, [], $tagsModeAll, DateRange::withStartDate(
Chronos::now()->subDays(2),
)));
}
/** @test */
@ -155,9 +165,14 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
$this->getEntityManager()->flush();
$result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([
'orderBy' => 'longUrl-ASC',
]));
$result = $this->repo->findList(
null,
null,
null,
[],
ShortUrlsParams::TAGS_MODE_ANY,
ShortUrlsOrdering::fromRawData(['orderBy' => 'longUrl-ASC']),
);
self::assertCount(count($urls), $result);
self::assertEquals('a', $result[0]->getLongUrl());

View file

@ -46,7 +46,8 @@ class ShortUrlRepositoryAdapterTest extends TestCase
$orderBy = $params->orderBy();
$dateRange = $params->dateRange();
$this->repo->findList(10, 5, $searchTerm, $tags, $orderBy, $dateRange, null)->shouldBeCalledOnce();
$this->repo->findList(10, 5, $searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $orderBy, $dateRange, null)
->shouldBeCalledOnce();
$adapter->getSlice(5, 10);
}
@ -70,7 +71,8 @@ class ShortUrlRepositoryAdapterTest extends TestCase
$adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params, $apiKey);
$dateRange = $params->dateRange();
$this->repo->countList($searchTerm, $tags, $dateRange, $apiKey->spec())->shouldBeCalledOnce();
$this->repo->countList($searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $dateRange, $apiKey->spec())
->shouldBeCalledOnce();
$adapter->getNbResults();
}