mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-22 08:56:42 -06:00
Added logic to actually filter short URLs by any tag or all tags
This commit is contained in:
parent
665a3dbcbf
commit
d8484e777f
@ -64,6 +64,12 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'A comma-separated list of tags to filter results.',
|
||||
)
|
||||
->addOption(
|
||||
'including-all-tags',
|
||||
'i',
|
||||
InputOption::VALUE_NONE,
|
||||
'If tags is provided, returns only short URLs having ALL tags.',
|
||||
)
|
||||
->addOption(
|
||||
'order-by',
|
||||
'o',
|
||||
@ -115,6 +121,9 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
|
||||
$page = (int) $input->getOption('page');
|
||||
$searchTerm = $input->getOption('search-term');
|
||||
$tags = $input->getOption('tags');
|
||||
$tagsMode = $input->getOption('including-all-tags') === true
|
||||
? ShortUrlsParams::TAGS_MODE_ALL
|
||||
: ShortUrlsParams::TAGS_MODE_ANY;
|
||||
$tags = ! empty($tags) ? explode(',', $tags) : [];
|
||||
$all = $input->getOption('all');
|
||||
$startDate = $this->getStartDateOption($input, $output);
|
||||
@ -125,6 +134,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
|
||||
$data = [
|
||||
ShortUrlsParamsInputFilter::SEARCH_TERM => $searchTerm,
|
||||
ShortUrlsParamsInputFilter::TAGS => $tags,
|
||||
ShortUrlsParamsInputFilter::TAGS_MODE => $tagsMode,
|
||||
ShortUrlsOrdering::ORDER_BY => $orderBy,
|
||||
ShortUrlsParamsInputFilter::START_DATE => $startDate?->toAtomString(),
|
||||
ShortUrlsParamsInputFilter::END_DATE => $endDate?->toAtomString(),
|
||||
|
@ -184,6 +184,7 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
?int $page,
|
||||
?string $searchTerm,
|
||||
array $tags,
|
||||
string $tagsMode,
|
||||
?string $startDate = null,
|
||||
?string $endDate = null,
|
||||
): void {
|
||||
@ -191,6 +192,7 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
'page' => $page,
|
||||
'searchTerm' => $searchTerm,
|
||||
'tags' => $tags,
|
||||
'tagsMode' => $tagsMode,
|
||||
'startDate' => $startDate !== null ? Chronos::parse($startDate)->toAtomString() : null,
|
||||
'endDate' => $endDate !== null ? Chronos::parse($endDate)->toAtomString() : null,
|
||||
]))->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
@ -203,20 +205,23 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
|
||||
public function provideArgs(): iterable
|
||||
{
|
||||
yield [[], 1, null, []];
|
||||
yield [['--page' => $page = 3], $page, null, []];
|
||||
yield [['--search-term' => $searchTerm = 'search this'], 1, $searchTerm, []];
|
||||
yield [[], 1, null, [], ShortUrlsParams::TAGS_MODE_ANY];
|
||||
yield [['--page' => $page = 3], $page, null, [], ShortUrlsParams::TAGS_MODE_ANY];
|
||||
yield [['--including-all-tags' => true], 1, null, [], ShortUrlsParams::TAGS_MODE_ALL];
|
||||
yield [['--search-term' => $searchTerm = 'search this'], 1, $searchTerm, [], ShortUrlsParams::TAGS_MODE_ANY];
|
||||
yield [
|
||||
['--page' => $page = 3, '--search-term' => $searchTerm = 'search this', '--tags' => $tags = 'foo,bar'],
|
||||
$page,
|
||||
$searchTerm,
|
||||
explode(',', $tags),
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
];
|
||||
yield [
|
||||
['--start-date' => $startDate = '2019-01-01'],
|
||||
1,
|
||||
null,
|
||||
[],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
$startDate,
|
||||
];
|
||||
yield [
|
||||
@ -224,6 +229,7 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
1,
|
||||
null,
|
||||
[],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
null,
|
||||
$endDate,
|
||||
];
|
||||
@ -232,6 +238,7 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
1,
|
||||
null,
|
||||
[],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
$startDate,
|
||||
$endDate,
|
||||
];
|
||||
@ -269,6 +276,7 @@ class ListShortUrlsCommandTest extends TestCase
|
||||
'page' => 1,
|
||||
'searchTerm' => null,
|
||||
'tags' => [],
|
||||
'tagsMode' => ShortUrlsParams::TAGS_MODE_ANY,
|
||||
'startDate' => null,
|
||||
'endDate' => null,
|
||||
'orderBy' => null,
|
||||
|
@ -16,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;
|
||||
@ -130,8 +131,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
|
||||
// Filter by tags if provided
|
||||
if (! empty($tags)) {
|
||||
$qb->join('s.tags', 't')
|
||||
->andWhere($qb->expr()->in('t.name', $tags));
|
||||
$tagsMode = $tagsMode ?? ShortUrlsParams::TAGS_MODE_ANY;
|
||||
$tagsMode === ShortUrlsParams::TAGS_MODE_ANY
|
||||
? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags))
|
||||
: $this->joinAllTags($qb, $tags);
|
||||
}
|
||||
|
||||
$this->applySpecification($qb, $spec, 's');
|
||||
@ -261,11 +264,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
return $qb->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
|
||||
foreach ($tags as $index => $tag) {
|
||||
$alias = 't_' . $index;
|
||||
$qb->join('s.tags', $alias, Join::WITH, $alias . '.name = :tag' . $index)
|
||||
->setParameter('tag' . $index, $tag);
|
||||
}
|
||||
$this->joinAllTags($qb, $tags);
|
||||
|
||||
// If tags where provided, we need an extra join to see the amount of tags that every short URL has, so that we
|
||||
// can discard those that also have more tags, making sure only those fully matching are included.
|
||||
@ -277,6 +276,15 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||
return $qb->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
|
||||
private function joinAllTags(QueryBuilder $qb, array $tags): void
|
||||
{
|
||||
foreach ($tags as $index => $tag) {
|
||||
$alias = 't_' . $index;
|
||||
$qb->join('s.tags', $alias, Join::WITH, $alias . '.name = :tag' . $index)
|
||||
->setParameter('tag' . $index, $tag);
|
||||
}
|
||||
}
|
||||
|
||||
public function findOneByImportedUrl(ImportedShlinkUrl $url): ?ShortUrl
|
||||
{
|
||||
$qb = $this->createQueryBuilder('s');
|
||||
|
@ -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;
|
||||
@ -174,6 +175,71 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
||||
self::assertEquals('z', $result[3]->getLongUrl());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function findListReturnsOnlyThoseWithMatchingTags(): void
|
||||
{
|
||||
$shortUrl1 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([
|
||||
'longUrl' => 'foo1',
|
||||
'tags' => ['foo', 'bar'],
|
||||
]), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl1);
|
||||
$shortUrl2 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([
|
||||
'longUrl' => 'foo2',
|
||||
'tags' => ['foo', 'baz'],
|
||||
]), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl2);
|
||||
$shortUrl3 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([
|
||||
'longUrl' => 'foo3',
|
||||
'tags' => ['foo'],
|
||||
]), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl3);
|
||||
$shortUrl4 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([
|
||||
'longUrl' => 'foo4',
|
||||
'tags' => ['bar', 'baz'],
|
||||
]), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl4);
|
||||
$shortUrl5 = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([
|
||||
'longUrl' => 'foo5',
|
||||
'tags' => ['bar', 'baz'],
|
||||
]), $this->relationResolver);
|
||||
$this->getEntityManager()->persist($shortUrl5);
|
||||
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
self::assertCount(5, $this->repo->findList(null, null, null, ['foo', 'bar']));
|
||||
self::assertCount(5, $this->repo->findList(null, null, null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ANY));
|
||||
self::assertCount(1, $this->repo->findList(null, null, null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ALL));
|
||||
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar']));
|
||||
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ANY));
|
||||
self::assertEquals(1, $this->repo->countList(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ALL));
|
||||
|
||||
self::assertCount(4, $this->repo->findList(null, null, null, ['bar', 'baz']));
|
||||
self::assertCount(4, $this->repo->findList(null, null, null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY));
|
||||
self::assertCount(2, $this->repo->findList(null, null, null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL));
|
||||
self::assertEquals(4, $this->repo->countList(null, ['bar', 'baz']));
|
||||
self::assertEquals(4, $this->repo->countList(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY));
|
||||
self::assertEquals(2, $this->repo->countList(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL));
|
||||
|
||||
self::assertCount(5, $this->repo->findList(null, null, null, ['foo', 'bar', 'baz']));
|
||||
self::assertCount(5, $this->repo->findList(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
['foo', 'bar', 'baz'],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
));
|
||||
self::assertCount(0, $this->repo->findList(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
['foo', 'bar', 'baz'],
|
||||
ShortUrlsParams::TAGS_MODE_ALL,
|
||||
));
|
||||
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar', 'baz']));
|
||||
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY));
|
||||
self::assertEquals(0, $this->repo->countList(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user